forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			448 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			448 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* 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"
 | |
| #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<NetworkConnectivityService> gConnService;
 | |
| 
 | |
| NetworkConnectivityService::NetworkConnectivityService()
 | |
|     : mLock("nat64prefixes") {}
 | |
| 
 | |
| // static
 | |
| already_AddRefed<NetworkConnectivityService>
 | |
| NetworkConnectivityService::GetSingleton() {
 | |
|   if (!XRE_IsParentProcess()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (gConnService) {
 | |
|     return do_AddRef(gConnService);
 | |
|   }
 | |
| 
 | |
|   RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService();
 | |
|   service->Init();
 | |
| 
 | |
|   gConnService = std::move(service);
 | |
|   ClearOnShutdown(&gConnService);
 | |
|   return do_AddRef(gConnService);
 | |
| }
 | |
| 
 | |
| nsresult NetworkConnectivityService::Init() {
 | |
|   nsCOMPtr<nsIObserverService> 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<AddrInfo> NetworkConnectivityService::MapNAT64IPs(
 | |
|     AddrInfo* aNewRRSet) {
 | |
|   // This should be called only if there are no IPv6 addresses.
 | |
| 
 | |
|   // Currently we only add prefixes to the first IP's clones.
 | |
|   uint32_t ip = aNewRRSet->Addresses()[0].inet.ip;
 | |
|   nsTArray<NetAddr> addresses = aNewRRSet->Addresses().Clone();
 | |
| 
 | |
|   MutexAutoLock lock(mLock);
 | |
|   size_t prefix_count = mNAT64Prefixes.Length();
 | |
|   for (size_t i = 0; i < prefix_count; i++) {
 | |
|     NetAddr addr = NetAddr(mNAT64Prefixes[i]);
 | |
| 
 | |
|     // 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();
 | |
| }
 | |
| 
 | |
| void NetworkConnectivityService::PerformChecks() {
 | |
|   mDNSv4 = UNKNOWN;
 | |
|   mDNSv6 = UNKNOWN;
 | |
|   mNAT64 = UNKNOWN;
 | |
| 
 | |
|   mIPv4 = UNKNOWN;
 | |
|   mIPv6 = UNKNOWN;
 | |
| 
 | |
|   RecheckDNS();
 | |
|   RecheckIPConnectivity();
 | |
| }
 | |
| 
 | |
| static inline void NotifyObservers(const char* aTopic) {
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   obs->NotifyObservers(nullptr, aTopic, nullptr);
 | |
| }
 | |
| 
 | |
| void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) {
 | |
|   nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
 | |
|   if (!rec) {
 | |
|     mNAT64 = UNKNOWN;
 | |
|     return;
 | |
|   }
 | |
|   MutexAutoLock lock(mLock);
 | |
|   mNAT64 = OK;
 | |
|   mNAT64Prefixes.Clear();
 | |
|   NetAddr addr{};
 | |
|   // use port 80 as dummy value for NetAddr
 | |
|   while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
 | |
|     if (addr.raw.family != AF_INET6 || IsIPAddrV4Mapped(&addr)) {
 | |
|       mNAT64Prefixes.Clear();
 | |
|       mNAT64 = NOT_AVAILABLE;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // RFC 7050 does not require the embedded IPv4 to be
 | |
|     // at the end of IPv6. In practice, and as we assume, it is.
 | |
|     // 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);
 | |
|   }
 | |
| 
 | |
|   if (mNAT64Prefixes.IsEmpty()) {
 | |
|     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.
 | |
| 
 | |
|   size_t length = mNAT64Prefixes.Length();
 | |
|   NetAddr prev = mNAT64Prefixes[0];
 | |
| 
 | |
|   for (size_t i = 1; i < length; i++) {
 | |
|     if (mNAT64Prefixes[i] == prev) {
 | |
|       mNAT64Prefixes.RemoveElementAt(i);
 | |
|       i--;
 | |
|       length--;
 | |
|     } else {
 | |
|       prev = mNAT64Prefixes[i];
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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;
 | |
|   } 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;
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIDNSService> 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_DISABLE_TRR,
 | |
|       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_DISABLE_TRR,
 | |
|       nullptr, this, NS_GetCurrentThread(), attrs,
 | |
|       getter_AddRefs(mDNSv6Request));
 | |
| 
 | |
|   if (StaticPrefs::network_connectivity_service_nat64_check()) {
 | |
|     rv = dns->AsyncResolveNative("ipv4only.arpa"_ns,
 | |
|                                  nsIDNSService::RESOLVE_TYPE_DEFAULT,
 | |
|                                  nsIDNSService::RESOLVE_DISABLE_IPV4 |
 | |
|                                      nsIDNSService::RESOLVE_DISABLE_TRR,
 | |
|                                  nullptr, this, NS_GetCurrentThread(), attrs,
 | |
|                                  getter_AddRefs(mNAT64Request));
 | |
|   }
 | |
|   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<nsIObserverService> 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<nsIChannel> 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<nsIURI> uri;
 | |
|   rv = NS_NewURI(getter_AddRefs(uri), url);
 | |
|   NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> channel;
 | |
|   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
 | |
| 
 | |
|   channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
 | |
| 
 | |
|   {
 | |
|     // Prevent HTTPS-Only Mode from upgrading the OCSP request.
 | |
|     nsCOMPtr<nsILoadInfo> 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<nsIHttpChannelInternal> 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<nsINetworkLinkService> 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
 | 
