chromium/chrome/browser/ash/net/network_diagnostics/arc_ping_routine.cc

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

#include <algorithm>
#include <string>
#include <utility>

#include "ash/components/arc/session/arc_service_manager.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/net/network_diagnostics/arc_ping_routine.h"
#include "chrome/browser/ash/net/network_diagnostics/network_diagnostics_util.h"
#include "chromeos/ash/services/network_config/in_process_instance.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"

namespace ash {
namespace network_diagnostics {

namespace {

namespace mojom = ::chromeos::network_diagnostics::mojom;
using ::chromeos::network_config::mojom::CrosNetworkConfig;
using ::chromeos::network_config::mojom::FilterType;
using ::chromeos::network_config::mojom::ManagedPropertiesPtr;
using ::chromeos::network_config::mojom::NetworkFilter;
using ::chromeos::network_config::mojom::NetworkStatePropertiesPtr;
using ::chromeos::network_config::mojom::NetworkType;

void GetNetworkConfigService(
    mojo::PendingReceiver<CrosNetworkConfig> receiver) {
  network_config::BindToInProcessInstance(std::move(receiver));
}

// Requests taking longer than 1500 ms are problematic.
constexpr int kProblemLatencyMs = 1500;

// Timeout for GetManagedProperties.
constexpr int kTimeoutGetManagedPropertiesSeconds = 180;

}  // namespace

ArcPingRoutine::ArcPingRoutine(mojom::RoutineCallSource source)
    : NetworkDiagnosticsRoutine(source) {
  set_verdict(mojom::RoutineVerdict::kNotRun);
  GetNetworkConfigService(
      remote_cros_network_config_.BindNewPipeAndPassReceiver());
}

ArcPingRoutine::~ArcPingRoutine() = default;

mojom::RoutineType ArcPingRoutine::Type() {
  return mojom::RoutineType::kArcPing;
}

void ArcPingRoutine::Run() {
  FetchActiveNetworks();
}

void ArcPingRoutine::AnalyzeResultsAndExecuteCallback() {
  if (unreachable_gateways_) {
    set_verdict(mojom::RoutineVerdict::kProblem);
    problems_.push_back(mojom::ArcPingProblem::kUnreachableGateway);
  } else if (failed_to_get_arc_service_manager_) {
    set_verdict(mojom::RoutineVerdict::kNotRun);
    problems_.push_back(mojom::ArcPingProblem::kFailedToGetArcServiceManager);
  } else if (failed_to_get_net_instance_service_for_ping_test_) {
    set_verdict(mojom::RoutineVerdict::kNotRun);
    problems_.push_back(
        mojom::ArcPingProblem::kFailedToGetNetInstanceForPingTest);
  } else if (get_managed_properties_timeout_failure_) {
    set_verdict(mojom::RoutineVerdict::kNotRun);
    problems_.push_back(
        mojom::ArcPingProblem::kGetManagedPropertiesTimeoutFailure);
  } else if (!pingable_default_network_) {
    set_verdict(mojom::RoutineVerdict::kProblem);
    problems_.push_back(mojom::ArcPingProblem::kFailedToPingDefaultNetwork);
  } else if (default_network_latency_ > kProblemLatencyMs) {
    set_verdict(mojom::RoutineVerdict::kProblem);
    problems_.push_back(
        mojom::ArcPingProblem::kDefaultNetworkAboveLatencyThreshold);
  } else if (non_default_network_unsuccessful_) {
    set_verdict(mojom::RoutineVerdict::kProblem);
    problems_.push_back(
        mojom::ArcPingProblem::kUnsuccessfulNonDefaultNetworksPings);
  } else if (non_default_max_latency_ > kProblemLatencyMs) {
    set_verdict(mojom::RoutineVerdict::kProblem);
    problems_.push_back(
        mojom::ArcPingProblem::kNonDefaultNetworksAboveLatencyThreshold);
  } else {
    set_verdict(mojom::RoutineVerdict::kNoProblem);
  }
  set_problems(mojom::RoutineProblems::NewArcPingProblems(problems_));
  ExecuteCallback();
}

void ArcPingRoutine::FetchActiveNetworks() {
  DCHECK(remote_cros_network_config_);
  remote_cros_network_config_->GetNetworkStateList(
      NetworkFilter::New(FilterType::kActive, NetworkType::kAll,
                         chromeos::network_config::mojom::kNoLimit),
      base::BindOnce(&ArcPingRoutine::OnNetworkStateListReceived,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ArcPingRoutine::FetchManagedProperties(
    const std::vector<std::string>& guids) {
  DCHECK(remote_cros_network_config_);
  guids_remaining_ = guids.size();

  // Post delayed task to handle timeout error on GetManagedProperties.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&ArcPingRoutine::HandleTimeout,
                     weak_ptr_factory_.GetWeakPtr()),
      base::Seconds(kTimeoutGetManagedPropertiesSeconds));

  for (const std::string& guid : guids) {
    remote_cros_network_config_->GetManagedProperties(
        guid, base::BindOnce(&ArcPingRoutine::OnManagedPropertiesReceived,
                             weak_ptr_factory_.GetWeakPtr()));
  }
}

void ArcPingRoutine::PingGateways() {
  arc::mojom::NetInstance* net_instance = GetNetInstance();
  if (net_instance) {
    for (size_t i = 0; i < gateways_.size(); i++) {
      net_instance->PingTest(
          gateways_transport_names_[i], gateways_[i],
          base::BindOnce(&ArcPingRoutine::OnRequestComplete,
                         weak_ptr_factory_.GetWeakPtr(),
                         gateways_[i] == default_network_gateway_));
    }
  }
}

void ArcPingRoutine::OnNetworkStateListReceived(
    std::vector<NetworkStatePropertiesPtr> networks) {
  bool connected = false;
  std::vector<std::string> guids;
  for (const auto& network : networks) {
    if (!chromeos::network_config::StateIsConnected(
            network->connection_state)) {
      continue;
    }
    connected = true;
    const std::string& guid = network->guid;
    if (default_network_guid_.empty()) {
      default_network_guid_ = guid;
    }
    guids.push_back(guid);
  }
  if (!connected || guids.empty()) {
    // Since we are not connected at all, directly analyze the results.
    AnalyzeResultsAndExecuteCallback();
  } else {
    FetchManagedProperties(guids);
  }
}

void ArcPingRoutine::OnManagedPropertiesReceived(
    ManagedPropertiesPtr managed_properties) {
  DCHECK(guids_remaining_ > 0);
  if (managed_properties && managed_properties->ip_configs.has_value() &&
      managed_properties->ip_configs->size() != 0) {
    for (const auto& ip_config : managed_properties->ip_configs.value()) {
      // Link-local addresses are not reachable from ARC, so skip them here.
      // TODO(b/277696397): Find a better signal.
      if (ip_config->gateway.has_value() &&
          !ip_config->gateway->starts_with("fe80::")) {
        const std::string& gateway = ip_config->gateway.value();
        if (managed_properties->guid == default_network_guid_) {
          default_network_gateway_ = gateway;
        }
        gateways_.push_back(gateway);
        gateways_transport_names_.push_back(
            GetTransportName(managed_properties->name->active_value));
      }
    }
  }
  guids_remaining_--;
  if (guids_remaining_ == 0) {
    weak_ptr_factory_.InvalidateWeakPtrs();
    if (gateways_.size() == 0) {
      // Since we cannot ping the gateway, directly analyze the results.
      AnalyzeResultsAndExecuteCallback();
    } else {
      unreachable_gateways_ = false;
      gateways_remaining_ = gateways_.size();
      PingGateways();
    }
  }
}

void ArcPingRoutine::OnRequestComplete(
    bool is_default_network_ping_result,
    arc::mojom::ArcPingTestResultPtr result) {
  DCHECK(gateways_remaining_ > 0);
  if (!result->is_successful) {
    if (!is_default_network_ping_result) {
      non_default_network_unsuccessful_ = true;
    }
  } else {
    if (is_default_network_ping_result) {
      pingable_default_network_ = true;
      default_network_latency_ = result->duration_ms;
    } else {
      non_default_max_latency_ =
          std::max(non_default_max_latency_, result->duration_ms);
    }
  }

  gateways_remaining_--;
  if (gateways_remaining_ == 0) {
    AnalyzeResultsAndExecuteCallback();
  }
}

void ArcPingRoutine::HandleTimeout() {
  // Destroy pending callbacks in case they end up being unexpectedly invoked
  // after the timer expires.
  weak_ptr_factory_.InvalidateWeakPtrs();
  get_managed_properties_timeout_failure_ = true;
  AnalyzeResultsAndExecuteCallback();
}

arc::mojom::NetInstance* ArcPingRoutine::GetNetInstance() {
  // If |net_instance_| is not already set for testing purposes, get instance
  // of NetInstance service.
  if (net_instance_) {
    return net_instance_;
  }

  // Call the singleton for ArcServiceManager and check if it is null.
  auto* arc_service_manager = arc::ArcServiceManager::Get();
  if (!arc_service_manager) {
    failed_to_get_arc_service_manager_ = true;
    AnalyzeResultsAndExecuteCallback();
    return nullptr;
  }

  // Get an instance of the NetInstance service and check if it is null.
  auto* arc_bridge_service = arc_service_manager->arc_bridge_service();
  arc::mojom::NetInstance* net_instance =
      ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service->net(), PingTest);
  if (!net_instance) {
    failed_to_get_net_instance_service_for_ping_test_ = true;
    AnalyzeResultsAndExecuteCallback();
    return nullptr;
  }
  return net_instance;
}

std::string ArcPingRoutine::GetTransportName(
    const std::string& managed_properties_name) {
  if (managed_properties_name == "wifi_guid") {
    return "wifi";
  } else if (managed_properties_name == "eth_guid") {
    return "eth";
  } else if (managed_properties_name == "cell_guid") {
    return "cell";
  } else if (managed_properties_name == "vpn_guid") {
    return "vpn";
  } else {
    return "";
  }
}

}  // namespace network_diagnostics
}  // namespace ash