netwerk/dns/TRRServiceBase.cpp
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Sun, 03 Jul 2022 06:58:40 +0000
changeset 622857 8d20c6660fa8cc3d7869f51e4cd15c185e1603e7
parent 603861 d35209ee525fd51dea5436eebc8139e994acdd35
permissions -rw-r--r--
no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD id -> 59fc812433a65f89acca5fd3d0b88885f2e0f7a1 ru -> f18dc88bad3435125ed914d1d6187eff10b14e46 tg -> 1b3ba16a0eca5b0ad55943e3bf5402c84cd211b5 uk -> d4a2520820de007c52009ff994289fe6fdb441cf

/* -*- 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 "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) && !defined(__MINGW32__)
#  include <shlobj_core.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;

  // 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() {
  // 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);

  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;
  mMode = ModeFromPrefs();
  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);
    }
  }

  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);

  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) && !defined(__MINGW32__)
    // 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");
#elif defined(__MINGW32__)
    nsAutoCString path("C:\\windows\\system32\\drivers\\etc\\hosts"_ns);
#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