diff options
Diffstat (limited to 'netwerk/dns/TRRServiceBase.cpp')
-rw-r--r-- | netwerk/dns/TRRServiceBase.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/netwerk/dns/TRRServiceBase.cpp b/netwerk/dns/TRRServiceBase.cpp new file mode 100644 index 0000000000..943edc41dd --- /dev/null +++ b/netwerk/dns/TRRServiceBase.cpp @@ -0,0 +1,399 @@ +/* -*- 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 <shlobj.h> // 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<nsIIOService> 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<nsIDNSService::ResolverMode>(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<nsIObserverService> 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<nsHttpConnectionInfo> 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<nsProxyInfo> proxyInfo = do_QueryInterface(aProxyInfo); + RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo( + host, port, ""_ns, username, proxyInfo, OriginAttributes(), isHttps); + bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo); + bool http3Allowed = proxyInfo ? proxyInfo->IsDirect() : true; + + RefPtr<AltSvcMapping> 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<nsIURI> 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<nsHttpConnectionInfo> 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<nsHttpConnectionInfo> TRRServiceBase::TRRConnectionInfo() { + RefPtr<nsHttpConnectionInfo> 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<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); + if (!pps) { + return; + } + + pps->AddProxyConfigCallback(this); +} + +void TRRServiceBase::UnregisterProxyChangeListener() { + if (!XRE_IsParentProcess()) { + return; + } + + nsCOMPtr<nsIProtocolProxyService> 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 |