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

#include "chrome/browser/net/dns_probe_runner.h"

#include <utility>

#include "base/bind.h"
#include "base/optional.h"
#include "mojo/public/cpp/bindings/message.h"
#include "net/base/address_list.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/public/resolve_error_info.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace chrome_browser_net {

const char DnsProbeRunner::kKnownGoodHostname[] = "google.com";

namespace {

DnsProbeRunner::Result EvaluateResponse(
    int net_error,
    const base::Optional<net::AddressList>& resolved_addresses) {
  switch (net_error) {
    case net::OK:
      break;

    case net::ERR_FAILED:
    // ERR_DNS_CACHE_MISS means HostResolver was not able to attempt DNS, e.g.
    // due to limitations from Chrome build configuration or because of
    // incompatibilities with the DNS configuration from the system. The result
    // Chrome would have gotten from DNS if attempted is therefore unknown.
    case net::ERR_DNS_CACHE_MISS:
      return DnsProbeRunner::UNKNOWN;

    // ERR_NAME_NOT_RESOLVED maps to NXDOMAIN, which means the server is working
    // but returned a wrong answer.
    case net::ERR_NAME_NOT_RESOLVED:
      return DnsProbeRunner::INCORRECT;

    // These results mean we heard *something* from the DNS server, but it was
    // unsuccessful (SERVFAIL) or malformed.
    case net::ERR_DNS_MALFORMED_RESPONSE:
    case net::ERR_DNS_SERVER_REQUIRES_TCP:  // Shouldn't happen; DnsTransaction
                                            // will retry with TCP.
    case net::ERR_DNS_SERVER_FAILED:
    case net::ERR_DNS_SORT_ERROR:  // Can only happen if the server responds.
      return DnsProbeRunner::FAILING;

    // Any other error means we never reached the DNS server in the first place.
    case net::ERR_DNS_TIMED_OUT:
    default:
      // Something else happened, probably at a network level.
      return DnsProbeRunner::UNREACHABLE;
  }

  if (!resolved_addresses) {
    // If net_error is OK, resolved_addresses should be set. The binding is not
    // closed here since it will be closed by the caller anyway.
    mojo::ReportBadMessage("resolved_addresses not set when net_error=OK");
    return DnsProbeRunner::UNKNOWN;
  } else if (resolved_addresses.value().empty()) {
    return DnsProbeRunner::INCORRECT;
  } else {
    return DnsProbeRunner::CORRECT;
  }
}

}  // namespace

DnsProbeRunner::DnsProbeRunner(
    net::DnsConfigOverrides dns_config_overrides,
    const NetworkContextGetter& network_context_getter)
    : dns_config_overrides_(dns_config_overrides),
      network_context_getter_(network_context_getter) {
  CreateHostResolver();
}

DnsProbeRunner::~DnsProbeRunner() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void DnsProbeRunner::RunProbe(base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!callback.is_null());
  DCHECK(host_resolver_);
  DCHECK(callback_.is_null());
  DCHECK(!receiver_.is_bound());

  network::mojom::ResolveHostParametersPtr parameters =
      network::mojom::ResolveHostParameters::New();
  parameters->dns_query_type = net::DnsQueryType::A;
  parameters->source = net::HostResolverSource::DNS;
  parameters->cache_usage =
      network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED;

  // Use transient NIKs - don't want cached responses anyways, so no benefit
  // from sharing a cache, beyond multiple probes not evicting anything.
  host_resolver_->ResolveHost(net::HostPortPair(kKnownGoodHostname, 80),
                              net::NetworkIsolationKey::CreateTransient(),
                              std::move(parameters),
                              receiver_.BindNewPipeAndPassRemote());
  receiver_.set_disconnect_handler(base::BindOnce(
      &DnsProbeRunner::OnMojoConnectionError, base::Unretained(this)));

  callback_ = std::move(callback);
}

bool DnsProbeRunner::IsRunning() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return !callback_.is_null();
}

void DnsProbeRunner::OnComplete(
    int32_t result,
    const net::ResolveErrorInfo& resolve_error_info,
    const base::Optional<net::AddressList>& resolved_addresses) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!callback_.is_null());

  result_ = EvaluateResponse(resolve_error_info.error, resolved_addresses);
  receiver_.reset();

  // ResolveHost will call OnComplete asynchronously, so callback_ can be
  // invoked directly here.  Clear callback in case it starts a new probe
  // immediately.
  std::move(callback_).Run();
}

void DnsProbeRunner::CreateHostResolver() {
  host_resolver_.reset();
  network_context_getter_.Run()->CreateHostResolver(
      dns_config_overrides_, host_resolver_.BindNewPipeAndPassReceiver());
}

void DnsProbeRunner::OnMojoConnectionError() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CreateHostResolver();
  OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
             base::nullopt);
}

}  // namespace chrome_browser_net
