chromium/chrome/browser/privacy/secure_dns_bridge.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <jni.h>

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/dns_probe_runner.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/secure_dns_util.h"
#include "chrome/browser/net/stub_resolver_config_reader.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/common/pref_names.h"
#include "components/country_codes/country_codes.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/dns/public/dns_over_https_config.h"
#include "net/dns/public/doh_provider_entry.h"
#include "net/dns/public/secure_dns_mode.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/privacy/jni_headers/SecureDnsBridge_jni.h"

using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
using chrome_browser_net::DnsProbeRunner;

namespace secure_dns = chrome_browser_net::secure_dns;

namespace {

net::DohProviderEntry::List GetFilteredProviders() {
  // Note: Check whether each provider is enabled *after* filtering based on
  // country code so that if we are doing experimentation via Finch for a
  // regional provider, the experiment groups will be less likely to include
  // users from other regions unnecessarily (since a client will be included in
  // the experiment if the provider feature flag is checked).
  return secure_dns::SelectEnabledProviders(secure_dns::ProvidersForCountry(
      net::DohProviderEntry::GetList(), country_codes::GetCurrentCountryID()));
}

// Runs a DNS probe according to the configuration in |overrides|,
// asynchronously sets |success| to indicate the result, and signals
// |waiter| when the probe has completed.  Must run on the UI thread.
void RunProbe(base::WaitableEvent* waiter,
              bool* success,
              const std::string& doh_config) {
  std::optional<net::DnsOverHttpsConfig> parsed =
      net::DnsOverHttpsConfig::FromString(doh_config);
  DCHECK(parsed.has_value());  // `doh_config` must be valid.
  auto* manager = g_browser_process->system_network_context_manager();
  auto runner = secure_dns::MakeProbeRunner(
      std::move(*parsed),
      base::BindRepeating(&SystemNetworkContextManager::GetContext,
                          base::Unretained(manager)));
  auto* const runner_ptr = runner.get();
  runner_ptr->RunProbe(base::BindOnce(
      [](base::WaitableEvent* waiter, bool* success,
         std::unique_ptr<DnsProbeRunner> runner) {
        *success = runner->result() == DnsProbeRunner::CORRECT;
        waiter->Signal();
      },
      waiter, success, std::move(runner)));
}

}  // namespace

static jint JNI_SecureDnsBridge_GetMode(JNIEnv* env) {
  return static_cast<int>(
      SystemNetworkContextManager::GetStubResolverConfigReader()
          ->GetSecureDnsConfiguration(
              true /* force_check_parental_controls_for_automatic_mode */)
          .mode());
}

static void JNI_SecureDnsBridge_SetMode(JNIEnv* env, jint mode) {
  PrefService* local_state = g_browser_process->local_state();
  local_state->SetString(
      prefs::kDnsOverHttpsMode,
      SecureDnsConfig::ModeToString(static_cast<net::SecureDnsMode>(mode)));
}

static jboolean JNI_SecureDnsBridge_IsModeManaged(JNIEnv* env) {
  PrefService* local_state = g_browser_process->local_state();
  return local_state->IsManagedPreference(prefs::kDnsOverHttpsMode);
}

static ScopedJavaLocalRef<jobjectArray> JNI_SecureDnsBridge_GetProviders(
    JNIEnv* env) {
  net::DohProviderEntry::List providers = GetFilteredProviders();
  std::vector<std::vector<std::u16string>> ret;
  ret.reserve(providers.size());
  base::ranges::transform(
      providers, std::back_inserter(ret),
      [](const net::DohProviderEntry* entry) -> std::vector<std::u16string> {
        net::DnsOverHttpsConfig config({entry->doh_server_config});
        return {base::UTF8ToUTF16(entry->ui_name),
                base::UTF8ToUTF16(config.ToString()),
                base::UTF8ToUTF16(entry->privacy_policy)};
      });
  return base::android::ToJavaArrayOfStringArray(env, ret);
}

static ScopedJavaLocalRef<jstring> JNI_SecureDnsBridge_GetConfig(JNIEnv* env) {
  PrefService* local_state = g_browser_process->local_state();
  return base::android::ConvertUTF8ToJavaString(
      env, local_state->GetString(prefs::kDnsOverHttpsTemplates));
}

static jboolean JNI_SecureDnsBridge_SetConfig(
    JNIEnv* env,
    const JavaParamRef<jstring>& jconfig) {
  PrefService* local_state = g_browser_process->local_state();
  std::string config = base::android::ConvertJavaStringToUTF8(jconfig);
  if (config.empty()) {
    local_state->ClearPref(prefs::kDnsOverHttpsTemplates);
    return true;
  }

  if (net::DnsOverHttpsConfig::FromString(config).has_value()) {
    local_state->SetString(prefs::kDnsOverHttpsTemplates, config);
    return true;
  }

  return false;
}

static jint JNI_SecureDnsBridge_GetManagementMode(JNIEnv* env) {
  return static_cast<int>(
      SystemNetworkContextManager::GetStubResolverConfigReader()
          ->GetSecureDnsConfiguration(
              true /* force_check_parental_controls_for_automatic_mode */)
          .management_mode());
}

static void JNI_SecureDnsBridge_UpdateValidationHistogram(JNIEnv* env,
                                                          jboolean valid) {
  secure_dns::UpdateValidationHistogram(valid);
}

static jboolean JNI_SecureDnsBridge_ProbeConfig(
    JNIEnv* env,
    const JavaParamRef<jstring>& doh_config) {
  // Android recommends converting async functions to blocking when using JNI:
  // https://developer.android.com/training/articles/perf-jni.
  // This function converts the DnsProbeRunner, which can only be created and
  // used on the UI thread, into a blocking function that can be called from an
  // auxiliary Java thread.
  // TODO: Use std::future if allowed in the future.
  base::WaitableEvent waiter;
  bool success;
  bool posted = content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(RunProbe, &waiter, &success,
                     base::android::ConvertJavaStringToUTF8(doh_config)));
  DCHECK(posted);
  waiter.Wait();

  secure_dns::UpdateProbeHistogram(success);
  return success;
}