chromium/chrome/browser/ash/net/network_diagnostics/video_conferencing_routine.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 "chrome/browser/ash/net/network_diagnostics/video_conferencing_routine.h"

#include <optional>
#include <string>
#include <utility>

#include "base/logging.h"
#include "base/time/time.h"
#include "chrome/browser/ash/net/network_diagnostics/network_diagnostics_util.h"
#include "chrome/browser/ash/net/network_diagnostics/udp_prober.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/net_errors.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace ash {
namespace network_diagnostics {

namespace {

namespace mojom = ::chromeos::network_diagnostics::mojom;

const char kDefaultStunServer[] = "stun.l.google.com";

}  // namespace

// TODO(crbug/1227877): Move support details to the UI.
const char kSupportDetails[] = "https://support.google.com/a/answer/1279090";
const base::TimeDelta kTimeoutAfterHostResolution = base::Seconds(10);

VideoConferencingRoutine::VideoConferencingRoutine(
    mojom::RoutineCallSource source)
    : NetworkDiagnosticsRoutine(source),
      stun_server_hostname_(kDefaultStunServer),
      udp_prober_getter_callback_(base::BindRepeating(
          &VideoConferencingRoutine::CreateAndExecuteUdpProber)),
      tls_prober_getter_callback_(base::BindRepeating(
          &VideoConferencingRoutine::CreateAndExecuteTlsProber)),
      udp_ports_(util::GetUdpPortsForGoogleStunServer()),
      tcp_ports_(util::GetTcpPortsForGoogleStunServer()),
      media_hostnames_(util::GetDefaultMediaUrls()) {}

VideoConferencingRoutine::VideoConferencingRoutine(
    mojom::RoutineCallSource source,
    const std::string& stun_server_hostname)
    : NetworkDiagnosticsRoutine(source),
      stun_server_hostname_(stun_server_hostname),
      udp_prober_getter_callback_(base::BindRepeating(
          &VideoConferencingRoutine::CreateAndExecuteUdpProber)),
      tls_prober_getter_callback_(base::BindRepeating(
          &VideoConferencingRoutine::CreateAndExecuteTlsProber)),
      udp_ports_(util::GetUdpPortsForCustomStunServer()),
      tcp_ports_(util::GetTcpPortsForCustomStunServer()),
      media_hostnames_(util::GetDefaultMediaUrls()) {}

VideoConferencingRoutine::~VideoConferencingRoutine() = default;

mojom::RoutineType VideoConferencingRoutine::Type() {
  return mojom::RoutineType::kVideoConferencing;
}

void VideoConferencingRoutine::Run() {
  ProbeStunServerOverUdp();
}

void VideoConferencingRoutine::AnalyzeResultsAndExecuteCallback() {
  std::optional<std::string> support_details = kSupportDetails;
  set_verdict(mojom::RoutineVerdict::kProblem);
  if (!open_udp_port_found_) {
    problems_.push_back(mojom::VideoConferencingProblem::kUdpFailure);
  }
  if (!open_tcp_port_found_) {
    problems_.push_back(mojom::VideoConferencingProblem::kTcpFailure);
  }
  if (!media_hostnames_reachable_) {
    problems_.push_back(mojom::VideoConferencingProblem::kMediaFailure);
  }
  if (problems_.empty()) {
    set_verdict(mojom::RoutineVerdict::kNoProblem);
    support_details = std::nullopt;
  }
  set_problems(mojom::RoutineProblems::NewVideoConferencingProblems(problems_));
  ExecuteCallback();
}


void VideoConferencingRoutine::ProbeStunServerOverUdp() {
  if (udp_ports_.empty()) {
    ProbeStunServerOverTcp();
    return;
  }
  int port = udp_ports_.back();
  udp_ports_.pop_back();
  AttemptUdpProbe(net::HostPortPair(stun_server_hostname_, port));
}

void VideoConferencingRoutine::ProbeStunServerOverTcp() {
  if (tcp_ports_.empty()) {
    ProbeMediaHostnames();
    return;
  }
  int port = tcp_ports_.back();
  tcp_ports_.pop_back();
  AttemptTcpProbe(net::HostPortPair(stun_server_hostname_, port));
}

void VideoConferencingRoutine::ProbeMediaHostnames() {
  if (media_hostnames_.empty()) {
    AnalyzeResultsAndExecuteCallback();
    return;
  }
  auto host_pair = net::HostPortPair::FromURL(media_hostnames_.back());
  media_hostnames_.pop_back();
  AttemptTlsProbe(host_pair);
}

network::mojom::NetworkContext* VideoConferencingRoutine::GetNetworkContext() {
  Profile* profile = util::GetUserProfile();
  DCHECK(profile);

  return profile->GetDefaultStoragePartition()->GetNetworkContext();
}

std::unique_ptr<UdpProber> VideoConferencingRoutine::CreateAndExecuteUdpProber(
    network::NetworkContextGetter network_context_getter,
    net::HostPortPair host_port_pair,
    base::span<const uint8_t> data,
    net::NetworkTrafficAnnotationTag tag,
    base::TimeDelta timeout_after_host_resolution,
    UdpProber::UdpProbeCompleteCallback callback) {
  return UdpProber::Start(std::move(network_context_getter), host_port_pair,
                          std::move(data), tag, timeout_after_host_resolution,
                          std::move(callback));
}

std::unique_ptr<TlsProber> VideoConferencingRoutine::CreateAndExecuteTlsProber(
    network::NetworkContextGetter network_context_getter,
    net::HostPortPair host_port_pair,
    bool negotiate_tls,
    TlsProber::TlsProbeCompleteCallback callback) {
  return std::make_unique<TlsProber>(std::move(network_context_getter),
                                     host_port_pair, negotiate_tls,
                                     std::move(callback));
}

void VideoConferencingRoutine::AttemptUdpProbe(
    net::HostPortPair host_port_pair) {
  // Store the instance of UdpProber.
  udp_prober_ = udp_prober_getter_callback_.Run(
      base::BindRepeating(&VideoConferencingRoutine::GetNetworkContext),
      host_port_pair, util::GetStunHeader(),
      util::GetStunNetworkAnnotationTag(), kTimeoutAfterHostResolution,
      base::BindOnce(&VideoConferencingRoutine::OnUdpProbeComplete,
                     weak_ptr()));
}

void VideoConferencingRoutine::AttemptTcpProbe(
    net::HostPortPair host_port_pair) {
  tls_prober_ = tls_prober_getter_callback_.Run(
      base::BindRepeating(&VideoConferencingRoutine::GetNetworkContext),
      host_port_pair,
      /*negotiate_tls=*/false,
      base::BindOnce(&VideoConferencingRoutine::OnTcpProbeComplete,
                     weak_ptr()));
}

void VideoConferencingRoutine::AttemptTlsProbe(
    net::HostPortPair host_port_pair) {
  // Store the instance of TlsProber.
  tls_prober_ = tls_prober_getter_callback_.Run(
      base::BindRepeating(&VideoConferencingRoutine::GetNetworkContext),
      host_port_pair,
      /*negotiate_tls=*/true,
      base::BindOnce(&VideoConferencingRoutine::OnTlsProbeComplete,
                     weak_ptr()));
}

void VideoConferencingRoutine::OnUdpProbeComplete(
    int result,
    UdpProber::ProbeExitEnum probe_exit_enum) {
  if (result == net::OK) {
    open_udp_port_found_ = true;
    // Only one open UDP port needs to be detected.
    ProbeStunServerOverTcp();
    return;
  }
  ProbeStunServerOverUdp();
}

void VideoConferencingRoutine::OnTcpProbeComplete(
    int result,
    TlsProber::ProbeExitEnum probe_exit_enum) {
  if (result == net::OK) {
    open_tcp_port_found_ = true;
    // Only one open TCP port needs to be detected.
    ProbeMediaHostnames();
    return;
  }
  ProbeStunServerOverTcp();
}

void VideoConferencingRoutine::OnTlsProbeComplete(
    int result,
    TlsProber::ProbeExitEnum probe_exit_enum) {
  if (result != net::OK) {
    media_hostnames_reachable_ = false;
    // All media hostnames must be reachable.
    AnalyzeResultsAndExecuteCallback();
    return;
  }
  ProbeMediaHostnames();
}

}  // namespace network_diagnostics
}  // namespace ash