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

#include "net/dns/loopback_only.h"

#include <optional>
#include <unordered_set>

#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "net/base/mock_network_change_notifier.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_LINUX)
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include "net/base/address_map_linux.h"
#endif  // BUILDFLAG(IS_LINUX)

namespace net {

#if BUILDFLAG(IS_LINUX)

namespace {

constexpr uint8_t kIpv4LoopbackBytes[] = {127, 0, 0, 1};
constexpr uint8_t kIpv4PrivateAddressBytes[] = {10, 0, 0, 1};
constexpr uint8_t kIpv6LoopbackBytes[] = {0, 0, 0, 0, 0, 0, 0, 0,
                                          0, 0, 0, 0, 0, 0, 0, 1};
constexpr uint8_t kIpv6AddressBytes[] = {0xFE, 0xDC, 0xBA, 0x98, 0, 0, 0, 0,
                                         0,    0,    0,    0,    0, 0, 0, 0};

constexpr uint8_t kIpv4LinkLocalBytes[] = {169, 254, 0, 0};
constexpr uint8_t kIpv4InIpv6LinkLocalBytes[] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 169, 254, 0, 0};
constexpr uint8_t kIpv6LinkLocalBytes[] = {0xFE, 0x80, 0, 0, 0, 0, 0, 0,
                                           0,    0,    0, 0, 0, 0, 0, 0};

class StubAddressMapOwnerLinux : public AddressMapOwnerLinux {
 public:
  AddressMap GetAddressMap() const override { return address_map_; }
  std::unordered_set<int> GetOnlineLinks() const override {
    return online_links_;
  }

  AddressMap& address_map() { return address_map_; }
  std::unordered_set<int>& online_links() { return online_links_; }

 private:
  AddressMap address_map_;
  std::unordered_set<int> online_links_;
};

bool GetResultOfRunHaveOnlyLoopbackAddressesJob() {
  bool result = false;
  net::TestClosure completion;
  {
    base::ScopedDisallowBlocking disallow_blocking;
    RunHaveOnlyLoopbackAddressesJob(
        base::BindLambdaForTesting([&](bool loopback_result) {
          result = loopback_result;
          completion.closure().Run();
        }));
  }
  completion.WaitForResult();
  return result;
}

}  // namespace

class LoopbackOnlyTest : public ::testing::Test {
 public:
  static constexpr int kTestInterfaceEth = 1;
  static constexpr int kTestInterfaceLoopback = 2;

  static inline const net::IPAddress kIpv4Loopback{kIpv4LoopbackBytes};
  static inline const net::IPAddress kIpv4PrivateAddress{
      kIpv4PrivateAddressBytes};

  static inline const net::IPAddress kIpv6Loopback{kIpv6LoopbackBytes};
  static inline const net::IPAddress kIpv6Address{kIpv6AddressBytes};

  static inline const net::IPAddress kIpv4LinkLocal{kIpv4LinkLocalBytes};
  static inline const net::IPAddress kIpv4InIpv6LinkLocal{
      kIpv4InIpv6LinkLocalBytes};
  static inline const net::IPAddress kIpv6LinkLocal{kIpv6LinkLocalBytes};

  LoopbackOnlyTest() {
    mock_notifier_.mock_network_change_notifier()->SetAddressMapOwnerLinux(
        &stub_address_map_owner_);
  }
  ~LoopbackOnlyTest() override = default;

 protected:
  base::test::TaskEnvironment task_environment_;
  test::ScopedMockNetworkChangeNotifier mock_notifier_;
  StubAddressMapOwnerLinux stub_address_map_owner_;
};

TEST_F(LoopbackOnlyTest, HasOnlyLoopbackIpv4) {
  // Include only a loopback interface.
  stub_address_map_owner_.address_map() = {
      {kIpv4Loopback, ifaddrmsg{
                          .ifa_family = AF_INET,
                          .ifa_flags = IFA_F_TEMPORARY,
                          .ifa_index = kTestInterfaceLoopback,
                      }}};
  // AddressTrackerLinux does not insert loopback interfaces into
  // `online_links`.
  stub_address_map_owner_.online_links() = {};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, HasActiveIPv4Connection) {
  stub_address_map_owner_.address_map() = {
      {kIpv4Loopback, ifaddrmsg{.ifa_family = AF_INET,
                                .ifa_flags = IFA_F_TEMPORARY,
                                .ifa_index = kTestInterfaceLoopback}},
      {kIpv4PrivateAddress, ifaddrmsg{.ifa_family = AF_INET,
                                      .ifa_flags = IFA_F_TEMPORARY,
                                      .ifa_index = kTestInterfaceEth}}};
  // `online_links` includes kTestInterfaceEth so that kIpv4PrivateAddress is
  // the active IPv4 connection. Also, AddressTrackerLinux does not insert
  // loopback interfaces into `online_links`.
  stub_address_map_owner_.online_links() = {kTestInterfaceEth};

  EXPECT_FALSE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, HasInactiveIPv4Connection) {
  stub_address_map_owner_.address_map() = {
      {kIpv4Loopback, ifaddrmsg{.ifa_family = AF_INET,
                                .ifa_flags = IFA_F_TEMPORARY,
                                .ifa_index = kTestInterfaceLoopback}},
      {kIpv4PrivateAddress, ifaddrmsg{.ifa_family = AF_INET,
                                      .ifa_flags = IFA_F_TEMPORARY,
                                      .ifa_index = kTestInterfaceEth}}};
  // `online_links` does not include kTestInterfaceEth so that
  // kIpv4PrivateAddress is the inactive IPv4 connection. Also,
  // AddressTrackerLinux does not insert loopback interfaces into
  // `online_links`.
  stub_address_map_owner_.online_links() = {};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, HasOnlyLoopbackIpv6) {
  // Include only a loopback interface.
  stub_address_map_owner_.address_map() = {
      {kIpv6Loopback, ifaddrmsg{
                          .ifa_family = AF_INET6,
                          .ifa_flags = IFA_F_TEMPORARY,
                          .ifa_index = kTestInterfaceLoopback,
                      }}};
  // AddressTrackerLinux does not insert loopback interfaces into
  // `online_links`.
  stub_address_map_owner_.online_links() = {};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, HasActiveIPv6Connection) {
  stub_address_map_owner_.address_map() = {
      {kIpv6Loopback, ifaddrmsg{.ifa_family = AF_INET6,
                                .ifa_flags = IFA_F_TEMPORARY,
                                .ifa_index = kTestInterfaceLoopback}},
      {kIpv6Address, ifaddrmsg{.ifa_family = AF_INET6,
                               .ifa_flags = IFA_F_TEMPORARY,
                               .ifa_index = kTestInterfaceEth}}};
  // `online_links` includes kTestInterfaceEth so that kIpv6Address is the
  // active IPv6 connection. Also, AddressTrackerLinux does not insert loopback
  // interfaces into `online_links`.
  stub_address_map_owner_.online_links() = {kTestInterfaceEth};

  EXPECT_FALSE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, HasInactiveIPv6Connection) {
  stub_address_map_owner_.address_map() = {
      {kIpv6Loopback, ifaddrmsg{.ifa_family = AF_INET6,
                                .ifa_flags = IFA_F_TEMPORARY,
                                .ifa_index = kTestInterfaceLoopback}},
      {kIpv6Address, ifaddrmsg{.ifa_family = AF_INET6,
                               .ifa_flags = IFA_F_TEMPORARY,
                               .ifa_index = kTestInterfaceEth}}};
  // `online_links` does not include kTestInterfaceEth so that kIpv6Address is
  // the inactive IPv6 connection. Also, AddressTrackerLinux does not insert
  // loopback interfaces into `online_links`.
  stub_address_map_owner_.online_links() = {};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, IPv6LinkLocal) {
  // Include only IPv6 link-local interfaces.
  stub_address_map_owner_.address_map() = {
      {kIpv6LinkLocal, ifaddrmsg{
                           .ifa_family = AF_INET6,
                           .ifa_flags = IFA_F_TEMPORARY,
                           .ifa_index = 3,
                       }}};
  // Mark the IPv6 link-local interface as online.
  stub_address_map_owner_.online_links() = {3};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

TEST_F(LoopbackOnlyTest, ExtraOnlineLinks) {
  // Include only IPv6 link-local interfaces.
  stub_address_map_owner_.address_map() = {
      {kIpv6LinkLocal, ifaddrmsg{
                           .ifa_family = AF_INET6,
                           .ifa_flags = IFA_F_TEMPORARY,
                           .ifa_index = 3,
                       }}};
  // AddressTrackerLinux should not give us online links other than the ones
  // listed in the AddressMap. However, it's better if this code is resilient to
  // a mismatch if there is a bug (for example if the kernel truncates the
  // messages or the buffer the AddressTrackerLinux provides to the kernel is
  // too small). And if this code runs on a different thread from the
  // AddressMapOwnerLinux, AddressMap and online links are updated separately,
  // and so it is possible they can be inconsistent with each other.
  stub_address_map_owner_.online_links() = {1, 2, 3};

  EXPECT_TRUE(GetResultOfRunHaveOnlyLoopbackAddressesJob());
}

// TODO(crbug.com/1450403): Test HaveOnlyLoopbackAddressesUsingGetifaddrs().

#endif  // BUILDFLAG(IS_LINUX)

}  // namespace net
