/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* 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 "TRRServiceBase.h" #include "TRRService.h" #include "mozilla/Preferences.h" #include "mozilla/ScopeExit.h" #include "nsHostResolver.h" #include "nsNetUtil.h" #include "nsIOService.h" #include "nsIDNSService.h" #include "nsIProxyInfo.h" #include "nsHttpConnectionInfo.h" #include "nsHttpHandler.h" #include "mozilla/StaticPrefs_network.h" #include "AlternateServices.h" #include "ProxyConfigLookup.h" // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. #include "DNSLogging.h" #if defined(XP_WIN) # include // for SHGetSpecialFolderPathA #endif // XP_WIN namespace mozilla { namespace net { NS_IMPL_ISUPPORTS(TRRServiceBase, nsIProxyConfigChangedCallback) TRRServiceBase::TRRServiceBase() : mDefaultTRRConnectionInfo("DataMutex::mDefaultTRRConnectionInfo") {} TRRServiceBase::~TRRServiceBase() { if (mTRRConnectionInfoInited) { UnregisterProxyChangeListener(); } } void TRRServiceBase::ProcessURITemplate(nsACString& aURI) { // URI Template, RFC 6570. if (aURI.IsEmpty()) { return; } nsAutoCString scheme; nsCOMPtr ios(do_GetIOService()); if (ios) { ios->ExtractScheme(aURI, scheme); } if (!scheme.Equals("https")) { LOG(("TRRService TRR URI %s is not https. Not used.\n", PromiseFlatCString(aURI).get())); aURI.Truncate(); return; } // cut off everything from "{" to "}" sequences (potentially multiple), // as a crude conversion from template into URI. nsAutoCString uri(aURI); do { nsCCharSeparatedTokenizer openBrace(uri, '{'); if (openBrace.hasMoreTokens()) { // the 'nextToken' is the left side of the open brace (or full uri) nsAutoCString prefix(openBrace.nextToken()); // if there is an open brace, there's another token const nsACString& endBrace = openBrace.nextToken(); nsCCharSeparatedTokenizer closeBrace(endBrace, '}'); if (closeBrace.hasMoreTokens()) { // there is a close brace as well, make a URI out of the prefix // and the suffix closeBrace.nextToken(); nsAutoCString suffix(closeBrace.nextToken()); uri = prefix + suffix; } else { // no (more) close brace break; } } else { // no (more) open brace break; } } while (true); aURI = uri; } void TRRServiceBase::CheckURIPrefs() { mURISetByDetection = false; if (StaticPrefs::network_trr_use_ohttp() && !mOHTTPURIPref.IsEmpty()) { MaybeSetPrivateURI(mOHTTPURIPref); return; } // The user has set a custom URI so it takes precedence. if (!mURIPref.IsEmpty()) { MaybeSetPrivateURI(mURIPref); return; } // Check if the rollout addon has set a pref. if (!mRolloutURIPref.IsEmpty()) { MaybeSetPrivateURI(mRolloutURIPref); return; } // Otherwise just use the default value. MaybeSetPrivateURI(mDefaultURIPref); } // static nsIDNSService::ResolverMode ModeFromPrefs( nsIDNSService::ResolverMode& aTRRModePrefValue) { // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved, // 5 - explicit off auto processPrefValue = [](uint32_t value) -> nsIDNSService::ResolverMode { if (value == nsIDNSService::MODE_RESERVED1 || value == nsIDNSService::MODE_RESERVED4 || value > nsIDNSService::MODE_TRROFF) { return nsIDNSService::MODE_TRROFF; } return static_cast(value); }; uint32_t tmp; if (NS_FAILED(Preferences::GetUint("network.trr.mode", &tmp))) { tmp = 0; } nsIDNSService::ResolverMode modeFromPref = processPrefValue(tmp); aTRRModePrefValue = modeFromPref; if (modeFromPref != nsIDNSService::MODE_NATIVEONLY) { return modeFromPref; } if (NS_FAILED(Preferences::GetUint(kRolloutModePref, &tmp))) { tmp = 0; } modeFromPref = processPrefValue(tmp); return modeFromPref; } void TRRServiceBase::OnTRRModeChange() { uint32_t oldMode = mMode; // This is the value of the pref "network.trr.mode" nsIDNSService::ResolverMode trrModePrefValue; mMode = ModeFromPrefs(trrModePrefValue); if (mMode != oldMode) { LOG(("TRR Mode changed from %d to %d", oldMode, int(mMode))); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC, nullptr); } TRRService::SetCurrentTRRMode(trrModePrefValue); } static bool readHosts = false; if ((mMode == nsIDNSService::MODE_TRRFIRST || mMode == nsIDNSService::MODE_TRRONLY) && !readHosts) { readHosts = true; ReadEtcHostsFile(); } } void TRRServiceBase::OnTRRURIChange() { Preferences::GetCString("network.trr.uri", mURIPref); Preferences::GetCString(kRolloutURIPref, mRolloutURIPref); Preferences::GetCString("network.trr.default_provider_uri", mDefaultURIPref); Preferences::GetCString("network.trr.ohttp.uri", mOHTTPURIPref); CheckURIPrefs(); } static already_AddRefed CreateConnInfoHelper( nsIURI* aURI, nsIProxyInfo* aProxyInfo) { MOZ_ASSERT(NS_IsMainThread()); nsAutoCString host; nsAutoCString scheme; nsAutoCString username; int32_t port = -1; bool isHttps = aURI->SchemeIs("https"); nsresult rv = aURI->GetScheme(scheme); if (NS_FAILED(rv)) { return nullptr; } rv = aURI->GetAsciiHost(host); if (NS_FAILED(rv)) { return nullptr; } rv = aURI->GetPort(&port); if (NS_FAILED(rv)) { return nullptr; } // Just a warning here because some nsIURIs do not implement this method. if (NS_WARN_IF(NS_FAILED(aURI->GetUsername(username)))) { LOG(("Failed to get username for aURI(%s)", aURI->GetSpecOrDefault().get())); } gHttpHandler->MaybeAddAltSvcForTesting(aURI, username, false, nullptr, OriginAttributes()); nsCOMPtr proxyInfo = do_QueryInterface(aProxyInfo); RefPtr connInfo = new nsHttpConnectionInfo( host, port, ""_ns, username, proxyInfo, OriginAttributes(), isHttps); bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo); bool http3Allowed = proxyInfo ? proxyInfo->IsDirect() : true; RefPtr mapping; if ((http2Allowed || http3Allowed) && AltSvcMapping::AcceptableProxy(proxyInfo) && (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) && (mapping = gHttpHandler->GetAltServiceMapping( scheme, host, port, false, OriginAttributes(), http2Allowed, http3Allowed))) { mapping->GetConnectionInfo(getter_AddRefs(connInfo), proxyInfo, OriginAttributes()); } return connInfo.forget(); } void TRRServiceBase::InitTRRConnectionInfo() { if (!XRE_IsParentProcess()) { return; } if (mTRRConnectionInfoInited) { return; } if (!NS_IsMainThread()) { NS_DispatchToMainThread(NS_NewRunnableFunction( "TRRServiceBase::InitTRRConnectionInfo", [self = RefPtr{this}]() { self->InitTRRConnectionInfo(); })); return; } LOG(("TRRServiceBase::InitTRRConnectionInfo")); nsAutoCString uri; GetURI(uri); AsyncCreateTRRConnectionInfoInternal(uri); } void TRRServiceBase::AsyncCreateTRRConnectionInfo(const nsACString& aURI) { LOG( ("TRRServiceBase::AsyncCreateTRRConnectionInfo " "mTRRConnectionInfoInited=%d", bool(mTRRConnectionInfoInited))); if (!mTRRConnectionInfoInited) { return; } AsyncCreateTRRConnectionInfoInternal(aURI); } void TRRServiceBase::AsyncCreateTRRConnectionInfoInternal( const nsACString& aURI) { if (!XRE_IsParentProcess()) { return; } SetDefaultTRRConnectionInfo(nullptr); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr dnsURI; nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), aURI); if (NS_FAILED(rv)) { return; } rv = ProxyConfigLookup::Create( [self = RefPtr{this}, uri(dnsURI)](nsIProxyInfo* aProxyInfo, nsresult aStatus) mutable { if (NS_FAILED(aStatus)) { self->SetDefaultTRRConnectionInfo(nullptr); return; } RefPtr connInfo = CreateConnInfoHelper(uri, aProxyInfo); self->SetDefaultTRRConnectionInfo(connInfo); if (!self->mTRRConnectionInfoInited) { self->mTRRConnectionInfoInited = true; self->RegisterProxyChangeListener(); } }, dnsURI, 0, nullptr); // mDefaultTRRConnectionInfo is set to nullptr at the beginning of this // method, so we don't really care aobut the |rv| here. If it's failed, // mDefaultTRRConnectionInfo stays as nullptr and we'll create a new // connection info in TRRServiceChannel again. Unused << NS_WARN_IF(NS_FAILED(rv)); } already_AddRefed TRRServiceBase::TRRConnectionInfo() { RefPtr connInfo; { auto lock = mDefaultTRRConnectionInfo.Lock(); connInfo = *lock; } return connInfo.forget(); } void TRRServiceBase::SetDefaultTRRConnectionInfo( nsHttpConnectionInfo* aConnInfo) { LOG(("TRRService::SetDefaultTRRConnectionInfo aConnInfo=%s", aConnInfo ? aConnInfo->HashKey().get() : "none")); { auto lock = mDefaultTRRConnectionInfo.Lock(); lock.ref() = aConnInfo; } } void TRRServiceBase::RegisterProxyChangeListener() { if (!XRE_IsParentProcess()) { return; } nsCOMPtr pps = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); if (!pps) { return; } pps->AddProxyConfigCallback(this); } void TRRServiceBase::UnregisterProxyChangeListener() { if (!XRE_IsParentProcess()) { return; } nsCOMPtr pps = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); if (!pps) { return; } pps->RemoveProxyConfigCallback(this); } void TRRServiceBase::DoReadEtcHostsFile(ParsingCallback aCallback) { MOZ_ASSERT(XRE_IsParentProcess()); if (!StaticPrefs::network_trr_exclude_etc_hosts()) { return; } auto readHostsTask = [aCallback]() { MOZ_ASSERT(!NS_IsMainThread(), "Must not run on the main thread"); #if defined(XP_WIN) // Inspired by libevent/evdns.c // Windows is a little coy about where it puts its configuration // files. Sure, they're _usually_ in C:\windows\system32, but // there's no reason in principle they couldn't be in // W:\hoboken chicken emergency nsCString path; path.SetLength(MAX_PATH + 1); if (!SHGetSpecialFolderPathA(NULL, path.BeginWriting(), CSIDL_SYSTEM, false)) { LOG(("Calling SHGetSpecialFolderPathA failed")); return; } path.SetLength(strlen(path.get())); path.Append("\\drivers\\etc\\hosts"); #else nsAutoCString path("/etc/hosts"_ns); #endif LOG(("Reading hosts file at %s", path.get())); rust_parse_etc_hosts(&path, aCallback); }; Unused << NS_DispatchBackgroundTask( NS_NewRunnableFunction("Read /etc/hosts file", readHostsTask), NS_DISPATCH_EVENT_MAY_BLOCK); } } // namespace net } // namespace mozilla