/* 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 "NetworkConnectivityService.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "xpcpublic.h" #include "nsSocketTransport2.h" #include "nsINetworkLinkService.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; // static already_AddRefed NetworkConnectivityService::GetSingleton() { if (!XRE_IsParentProcess()) { return nullptr; } 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; } void NetworkConnectivityService::PerformChecks() { mDNSv4 = UNKNOWN; mDNSv6 = UNKNOWN; mIPv4 = UNKNOWN; mIPv6 = UNKNOWN; RecheckDNS(); RecheckIPConnectivity(); } static inline void NotifyObservers(const char* aTopic) { nsCOMPtr obs = mozilla::services::GetObserverService(); obs->NotifyObservers(nullptr, aTopic, nullptr); } NS_IMETHODIMP NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, nsresult aStatus) { ConnectivityState state = aRecord ? OK : NOT_AVAILABLE; if (aRequest == mDNSv4Request) { mDNSv4 = state; mDNSv4Request = nullptr; } else if (aRequest == mDNSv6Request) { mDNSv6 = state; mDNSv6Request = nullptr; } if (!mDNSv4Request && !mDNSv6Request) { 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; } 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_DISABLE_IPV6 | nsIDNSService::RESOLVE_DISABLE_TRR, 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_DISABLE_IPV4 | nsIDNSService::RESOLVE_DISABLE_TRR, this, NS_GetCurrentThread(), attrs, getter_AddRefs(mDNSv6Request)); 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; } 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; } static inline already_AddRefed 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; rv = NS_NewChannel( getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_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 channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE); { // 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); } 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 (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