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

#include <cstdint>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/raw_span.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/net/network_diagnostics/fake_network_context.h"
#include "chrome/browser/ash/net/network_diagnostics/network_diagnostics_util.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/test/test_network_context.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::network_diagnostics {

namespace {

using ProbeExitEnum = UdpProber::ProbeExitEnum;

}  // namespace

class UdpProberWithFakeNetworkContextTest : public ::testing::Test {
 public:
  UdpProberWithFakeNetworkContextTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  UdpProberWithFakeNetworkContextTest(
      const UdpProberWithFakeNetworkContextTest&) = delete;
  UdpProberWithFakeNetworkContextTest& operator=(
      const UdpProberWithFakeNetworkContextTest&) = delete;

  void InitializeProberNetworkContext(
      std::unique_ptr<FakeNetworkContext::DnsResult> fake_dns_result,
      std::optional<net::Error> udp_connect_complete_code,
      std::optional<net::Error> udp_send_complete_code,
      std::optional<net::Error> udp_on_received_code,
      std::optional<base::span<const uint8_t>> udp_on_received_data) {
    fake_network_context_ = std::make_unique<FakeNetworkContext>();
    fake_network_context_->set_fake_dns_result(std::move(fake_dns_result));
    if (udp_connect_complete_code.has_value()) {
      fake_network_context_->SetUdpConnectCode(
          udp_connect_complete_code.value());
    }
    if (udp_send_complete_code.has_value()) {
      fake_network_context_->SetUdpSendCode(udp_send_complete_code.value());
    }
    if (udp_on_received_code.has_value()) {
      fake_network_context_->SetUdpOnReceivedCode(udp_on_received_code.value());
    }
    if (udp_on_received_data.has_value()) {
      fake_network_context_->SetUdpOnReceivedData(
          std::move(udp_on_received_data.value()));
    }
  }

  void SetUdpDelays(std::optional<base::TimeDelta> connection_delay,
                    std::optional<base::TimeDelta> send_delay,
                    std::optional<base::TimeDelta> receive_delay) {
    fake_network_context_->SetTaskEnvironmentForTesting(&task_environment_);
    if (connection_delay.has_value()) {
      fake_network_context_->SetUdpConnectionDelay(connection_delay.value());
    }
    if (send_delay.has_value()) {
      fake_network_context_->SetUdpSendDelay(send_delay.value());
    }
    if (receive_delay.has_value()) {
      fake_network_context_->SetUdpReceiveDelay(receive_delay.value());
    }
  }

  void CreateAndExecuteUdpProber(base::span<const uint8_t> data,
                                 UdpProber::UdpProbeCompleteCallback callback) {
    ASSERT_TRUE(fake_network_context_);
    udp_prober_ = UdpProber::Start(
        base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
          return fake_network_context();
        }),
        kFakeHostPortPair, std::move(data), kStunTag,
        kTimeoutAfterHostResolution, std::move(callback));
  }

  void RunProberExpectingResult(int expected_result,
                                ProbeExitEnum expected_exit_enum) {
    base::test::TestFuture<int, ProbeExitEnum> future;
    CreateAndExecuteUdpProber(kValidStunData, future.GetCallback());
    auto [probe_result, probe_exit_enum] = future.Take();
    ASSERT_EQ(probe_result, expected_result);
    ASSERT_EQ(probe_exit_enum, expected_exit_enum);
  }

  FakeNetworkContext* fake_network_context() {
    return fake_network_context_.get();
  }

 protected:
  const net::HostPortPair kFakeHostPortPair =
      net::HostPortPair::FromString("fake_stun_server.com:80");
  const net::IPEndPoint kFakeIPAddress{
      net::IPEndPoint(net::IPAddress::IPv4Localhost(), /*port=*/1234)};
  const base::raw_span<const uint8_t> kValidStunData = util::GetStunHeader();
  const net::NetworkTrafficAnnotationTag kStunTag =
      util::GetStunNetworkAnnotationTag();
  const base::TimeDelta kTimeoutAfterHostResolution = base::Seconds(10);

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<FakeNetworkContext> fake_network_context_;
  std::unique_ptr<UdpProber> udp_prober_;
};

TEST_F(UdpProberWithFakeNetworkContextTest, SuccessfulEndToEndResponse) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  std::array<uint8_t, 1> udp_on_received_data = {0x00};
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_complete_code=*/net::OK,
                                 /*udp_on_received_code=*/net::OK,
                                 udp_on_received_data);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/base::Seconds(1));
  RunProberExpectingResult(net::OK, ProbeExitEnum::kSuccess);
}

TEST_F(UdpProberWithFakeNetworkContextTest, FailedDnsLookup) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::ERR_NAME_NOT_RESOLVED,
      net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED),
      /*resolved_addresses=*/std::nullopt,
      /*endpoint_results_with_metadata=*/std::nullopt);
  // UDP connect and subsequent steps will not happen in this scenario.
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/std::nullopt,
                                 /*udp_send_complete_code=*/std::nullopt,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/std::nullopt);
  RunProberExpectingResult(net::ERR_NAME_NOT_RESOLVED,
                           ProbeExitEnum::kDnsFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDnsLookup) {
  // UDP connect and subsequent steps will not happen in this scenario.
  InitializeProberNetworkContext(/*fake_dns_result=*/{},
                                 /*udp_connect_code=*/std::nullopt,
                                 /*udp_send_code=*/std::nullopt,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/std::nullopt);
  fake_network_context()->set_disconnect_during_host_resolution(true);
  RunProberExpectingResult(net::ERR_FAILED, ProbeExitEnum::kDnsFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpConnection) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(
      std::move(fake_dns_result),
      /*udp_connect_code=*/net::ERR_CONNECTION_FAILED,
      /*udp_send_code=*/std::nullopt,
      /*udp_on_received_code=*/std::nullopt,
      /*udp_on_received_data=*/std::nullopt);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/std::nullopt,
               /*receive_delay=*/std::nullopt);
  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
                           ProbeExitEnum::kConnectFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpConnection) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_code=*/std::nullopt,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/std::nullopt);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/std::nullopt,
               /*receive_delay=*/std::nullopt);
  fake_network_context()->set_disconnect_during_udp_connection_attempt(true);
  RunProberExpectingResult(net::ERR_FAILED,
                           ProbeExitEnum::kMojoDisconnectFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpSend) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_code=*/net::ERR_CONNECTION_FAILED,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/std::nullopt);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/std::nullopt);
  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
                           ProbeExitEnum::kSendFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpSend) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  net::Error udp_connect_code = net::OK;
  InitializeProberNetworkContext(std::move(fake_dns_result), udp_connect_code,
                                 /*udp_send_code=*/std::nullopt,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/std::nullopt);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/std::nullopt);
  fake_network_context()->SetDisconnectDuringUdpSendAttempt(true);
  RunProberExpectingResult(net::ERR_FAILED,
                           ProbeExitEnum::kMojoDisconnectFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, BadUdpNetworkCodeOnReceive) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(
      std::move(fake_dns_result),
      /*udp_connect_code=*/net::OK,
      /*udp_send_code=*/net::OK,
      /*udp_on_received_code=*/net::ERR_CONNECTION_FAILED,
      /*udp_on_received_data=*/std::nullopt);
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/base::Seconds(1));
  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
                           ProbeExitEnum::kNetworkErrorOnReceiveFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, NoDataReceivedOnReceiveFailure) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_code=*/net::OK,
                                 /*udp_on_received_code*/ net::OK,
                                 /*udp_on_received_data=*/{});
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/base::Seconds(1));
  RunProberExpectingResult(net::ERR_FAILED,
                           ProbeExitEnum::kNoDataReceivedFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpReceive) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_code=*/net::OK,
                                 /*udp_on_received_code=*/net::OK,
                                 /*udp_on_received_data=*/{});
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/base::Seconds(1));
  fake_network_context()->SetDisconnectDuringUdpReceiveAttempt(true);
  RunProberExpectingResult(net::ERR_FAILED,
                           ProbeExitEnum::kMojoDisconnectFailure);
}

TEST_F(UdpProberWithFakeNetworkContextTest, ProbeTimeoutDuringUdpConnection) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_complete_code=*/std::nullopt,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/{});
  SetUdpDelays(/*connection_delay=*/base::Seconds(15),
               /*send_delay=*/std::nullopt,
               /*receive_delay=*/std::nullopt);
  RunProberExpectingResult(net::ERR_TIMED_OUT, ProbeExitEnum::kTimeout);
}

TEST_F(UdpProberWithFakeNetworkContextTest, ProbeTimeoutDuringUdpSend) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_complete_code=*/net::OK,
                                 /*udp_on_received_code=*/std::nullopt,
                                 /*udp_on_received_data=*/{});
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(15),
               /*receive_delay=*/std::nullopt);
  RunProberExpectingResult(net::ERR_TIMED_OUT, ProbeExitEnum::kTimeout);
}

TEST_F(UdpProberWithFakeNetworkContextTest, ProbeTimeoutDuringUdpReceive) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress),
      /*endpoint_results_with_metadata=*/std::nullopt);
  InitializeProberNetworkContext(std::move(fake_dns_result),
                                 /*udp_connect_code=*/net::OK,
                                 /*udp_send_complete_code=*/net::OK,
                                 /*udp_on_received_code=*/net::OK,
                                 /*udp_on_received_data=*/{});
  SetUdpDelays(/*connection_delay=*/base::Seconds(1),
               /*send_delay=*/base::Seconds(1),
               /*receive_delay=*/base::Seconds(15));
  RunProberExpectingResult(net::ERR_TIMED_OUT, ProbeExitEnum::kTimeout);
}

}  // namespace ash::network_diagnostics