910e62b5创建于 1月15日历史提交
// 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 <linux/if.h>
#include <linux/if_addr.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>

#include <memory>
#include <unordered_set>
#include <vector>

#include "base/functional/bind.h"
#include "base/posix/unix_domain_socket.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/scoped_feature_list.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/address_map_linux.h"
#include "net/base/address_tracker_linux_test_util.h"
#include "net/base/features.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_change_notifier_factory.h"
#include "net/base/network_change_notifier_linux.h"
#include "services/network/public/mojom/network_change_manager.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {
constexpr unsigned char kAddress0[] = {127, 0, 0, 1};
constexpr unsigned char kAddress1[] = {10, 0, 0, 1};
constexpr unsigned char kAddress2[] = {192, 168, 0, 1};
constexpr unsigned char kAddress3[] = {0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 1};

class NCNLinuxMockedNetlinkTestUtil {
 public:
  static constexpr int kTestInterfaceEth = 1;
  static constexpr int kTestInterfaceOther = 2;
  static inline const net::IPAddress kEmpty;
  static inline const net::IPAddress kAddr0{kAddress0};
  static inline const net::IPAddress kAddr1{kAddress1};
  static inline const net::IPAddress kAddr2{kAddress2};
  static inline const net::IPAddress kAddr3{kAddress3};

  NCNLinuxMockedNetlinkTestUtil() = default;
  ~NCNLinuxMockedNetlinkTestUtil() = default;

  std::unique_ptr<net::NetworkChangeNotifierLinux> CreateNCNLinux() {
    base::ScopedFD netlink_fd_receiver;
    base::CreateSocketPair(&fake_netlink_fd_, &netlink_fd_receiver);
    auto ncn_linux =
        net::NetworkChangeNotifierLinux::CreateWithSocketForTesting(
            {}, std::move(netlink_fd_receiver));

    base::ThreadPool::PostTaskAndReply(
        FROM_HERE, {base::MayBlock()},
        base::BindOnce(
            &NCNLinuxMockedNetlinkTestUtil::SimulateAddressTrackerLinuxStart,
            base::Unretained(this)),
        base::BindOnce(&NCNLinuxMockedNetlinkTestUtil::SetInitialized,
                       base::Unretained(this)));

    return ncn_linux;
  }

  // This should run on a thread pool thread because it blocks.
  // Sets up the AddressMap with kAddr0, and kTestInterfaceEth online.
  void SimulateAddressTrackerLinuxStart() {
    struct {
      struct nlmsghdr header;
      struct rtgenmsg msg;
    } request = {};

    // Receive the RTM_GETADDR request.
    std::vector<base::ScopedFD> fds;
    ssize_t expected_size = NLMSG_LENGTH(sizeof(request.msg));
    EXPECT_EQ(base::UnixDomainSocket::RecvMsg(fake_netlink_fd_.get(), &request,
                                              expected_size, &fds),
              expected_size);
    EXPECT_TRUE(fds.empty());
    EXPECT_EQ(request.header.nlmsg_type, RTM_GETADDR);

    // Send a response.
    net::test::NetlinkBuffer buffer;
    net::test::MakeAddrMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET,
                               kTestInterfaceEth, kAddr0, kEmpty, &buffer);
    base::UnixDomainSocket::SendMsg(fake_netlink_fd_.get(), buffer.data(),
                                    buffer.size(), {});

    // Receive the RTM_GETLINK request.
    EXPECT_EQ(base::UnixDomainSocket::RecvMsg(fake_netlink_fd_.get(), &request,
                                              expected_size, &fds),
              expected_size);
    EXPECT_EQ(request.header.nlmsg_type, RTM_GETLINK);

    // Send a response.
    buffer.clear();
    net::test::MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING,
                               kTestInterfaceEth, &buffer);
    base::UnixDomainSocket::SendMsg(fake_netlink_fd_.get(), buffer.data(),
                                    buffer.size(), {});
  }

  void BufferAddAddrMsg(const net::IPAddress address,
                        int interface = kTestInterfaceEth,
                        uint8_t flags = IFA_F_TEMPORARY) {
    net::test::MakeAddrMessage(RTM_NEWADDR, flags,
                               address.IsIPv4() ? AF_INET : AF_INET6, interface,
                               address, kEmpty, &buffer_);
  }

  void BufferDeleteAddrMsg(const net::IPAddress& address,
                           int interface = kTestInterfaceEth) {
    net::test::MakeAddrMessage(RTM_DELADDR, 0,
                               address.IsIPv4() ? AF_INET : AF_INET6, interface,
                               address, kEmpty, &buffer_);
  }

  void BufferAddLinkMsg(int link) {
    net::test::MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING,
                               link, &buffer_, /*clear_output=*/false);
  }

  void BufferDeleteLinkMsg(int link) {
    net::test::MakeLinkMessage(RTM_DELLINK, 0, link, &buffer_,
                               /*clear_output=*/false);
  }

  void SendBuffer() {
    base::UnixDomainSocket::SendMsg(fake_netlink_fd_.get(), buffer_.data(),
                                    buffer_.size(), {});
    buffer_.clear();
  }

  void SetInitialized() {
    initialized_ = true;
    initialize_run_loop_.Quit();
  }

  void WaitForInit() {
    if (initialized_) {
      return;
    }
    initialize_run_loop_.Run();
  }

 private:
  base::ScopedFD fake_netlink_fd_;

  bool initialized_ = false;
  base::RunLoop initialize_run_loop_;

  net::test::NetlinkBuffer buffer_;
};

class NetworkChangeNotifierLinuxMockedNetlinkFactory
    : public net::NetworkChangeNotifierFactory {
 public:
  std::unique_ptr<net::NetworkChangeNotifier> CreateInstanceWithInitialTypes(
      net::NetworkChangeNotifier::ConnectionType /*initial_type*/,
      net::NetworkChangeNotifier::ConnectionSubtype /*nitial_subtype*/)
      override {
    // There should only be one called to this factory function.
    DCHECK(!ncn_wrapper_);
    ncn_wrapper_ = std::make_unique<NCNLinuxMockedNetlinkTestUtil>();
    return ncn_wrapper_->CreateNCNLinux();
  }

  NCNLinuxMockedNetlinkTestUtil* ncn_wrapper() { return ncn_wrapper_.get(); }

 private:
  std::unique_ptr<NCNLinuxMockedNetlinkTestUtil> ncn_wrapper_;
};

enum class ExpectedConnectionType {
  kNone,
  kConnected,
};

class AddressMapLinuxBrowserTest : public ContentBrowserTest {
 public:
  struct ExpectedCachedInfo {
    std::vector<net::IPAddress> should_contain_addresses;
    std::vector<net::IPAddress> should_not_contain_addresses;
    std::vector<int> should_contain_links;
    std::vector<int> should_not_contain_links;
  };

  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        net::features::kAddressTrackerLinuxIsProxied);
    ForceOutOfProcessNetworkService();
    ncn_mocked_factory_ =
        std::make_unique<NetworkChangeNotifierLinuxMockedNetlinkFactory>();
    net::NetworkChangeNotifier::SetFactory(ncn_mocked_factory_.get());
    ContentBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    mojo::Remote<network::mojom::NetworkChangeManager> network_change_manager;
    GetNetworkService()->GetNetworkChangeManager(
        network_change_manager.BindNewPipeAndPassReceiver());

    mojo::PendingReceiver<network::mojom::NetworkChangeManagerClient>
        client_receiver;
    network_change_manager->RequestNotifications(
        client_receiver.InitWithNewPipeAndPassRemote());
    notification_listener_ =
        std::make_unique<NetworkChangeNotificationListener>(
            std::move(client_receiver));
  }

  void ExpectCorrectInfoInNetworkService(
      ExpectedCachedInfo expected_cached_info) {
    const net::AddressMapOwnerLinux* address_map_owner =
        net::NetworkChangeNotifier::GetAddressMapOwner();

    net::AddressMapOwnerLinux::AddressMap network_service_addr_map;
    std::unordered_set<int> network_service_links;
    {
      mojo::ScopedAllowSyncCallForTesting allow_sync_call;
      network_service_test()->GetAddressMapCacheLinux(&network_service_addr_map,
                                                      &network_service_links);
    }

    net::AddressMapOwnerLinux::AddressMap browser_process_addr_map =
        address_map_owner->GetAddressMap();
    std::unordered_set<int> browser_process_links =
        address_map_owner->GetOnlineLinks();
    EXPECT_EQ(browser_process_addr_map, network_service_addr_map);
    EXPECT_EQ(browser_process_links, network_service_links);

    for (const net::IPAddress& address :
         expected_cached_info.should_contain_addresses) {
      SCOPED_TRACE(testing::Message()
                   << "Network service AddressMap should include "
                   << address.ToString());
      EXPECT_TRUE(network_service_addr_map.contains(address));
    }
    for (const net::IPAddress& address :
         expected_cached_info.should_not_contain_addresses) {
      SCOPED_TRACE(testing::Message()
                   << "Network service AddressMap should not include "
                   << address.ToString());
      EXPECT_FALSE(network_service_addr_map.contains(address));
    }
    for (const int link : expected_cached_info.should_contain_links) {
      SCOPED_TRACE(testing::Message()
                   << "Network service online links should include " << link);
      EXPECT_TRUE(network_service_links.contains(link));
    }
    for (const int link : expected_cached_info.should_not_contain_links) {
      SCOPED_TRACE(testing::Message()
                   << "Network service online links should not include "
                   << link);
      EXPECT_FALSE(network_service_links.contains(link));
    }
  }

  void WaitForNetworkChange(ExpectedConnectionType expected_connection_type) {
    notification_listener_->WaitForNetworkChange(expected_connection_type);
  }

 protected:
  std::unique_ptr<NetworkChangeNotifierLinuxMockedNetlinkFactory>
      ncn_mocked_factory_;

 private:
  class NetworkChangeNotificationListener
      : public network::mojom::NetworkChangeManagerClient {
   public:
    explicit NetworkChangeNotificationListener(
        mojo::PendingReceiver<network::mojom::NetworkChangeManagerClient>
            receiver)
        : receiver_(this, std::move(receiver)) {}
    void OnInitialConnectionType(network::mojom::ConnectionType type) override {
    }

    void OnNetworkChanged(network::mojom::ConnectionType type) override {
      // NetworkChangeNotifier::NetworkChangeObserver will fire a
      // CONNECTION_NONE change right before firing a non-CONNECTION_NONE
      // change. So if this is a CONNECTION_NONE event, only continue the test
      // if the test is expecting a CONNECTION_NONE event.
      // TODO(mpdenton): set timeouts to zero in the network process so tests
      // run faster.
      if ((expected_connection_type_ == ExpectedConnectionType::kNone ||
           type != network::mojom::ConnectionType::CONNECTION_NONE) &&
          run_loop_.has_value()) {
        run_loop_->Quit();
      }
    }

    void WaitForNetworkChange(ExpectedConnectionType expected_connection_type) {
      expected_connection_type_ = expected_connection_type;
      run_loop_.emplace();
      run_loop_->Run();
      run_loop_.reset();
    }

   private:
    mojo::Receiver<network::mojom::NetworkChangeManagerClient> receiver_;
    std::optional<base::RunLoop> run_loop_;
    ExpectedConnectionType expected_connection_type_;
  };

  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<NetworkChangeNotificationListener> notification_listener_;
};
}  // namespace

IN_PROC_BROWSER_TEST_F(AddressMapLinuxBrowserTest, CheckInitialMapsMatch) {
  if (IsInProcessNetworkService()) {
    GTEST_SKIP();
  }

  ncn_mocked_factory_->ncn_wrapper()->WaitForInit();

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});
}

IN_PROC_BROWSER_TEST_F(AddressMapLinuxBrowserTest, CheckAddressMapDiffsApply) {
  // Delete kAddr0 from the map.
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr0);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kNone);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {},
       .should_not_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});

  // Now add kAddr0 back to the map.
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr0);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kConnected);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});

  // Now change kAddr0's ifaddrmsg. Use flags = IFA_F_HOMEADDRESS rather than
  // flags = IFA_F_TEMPORARY.
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr0,
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth, IFA_F_HOMEADDRESS);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kConnected);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});

  // Add the other addresses, and then delete one of them and delete the
  // existing address before sending diffs.
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr1);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr2);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr3);

  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr2);
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr0);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kConnected);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr1,
                                    NCNLinuxMockedNetlinkTestUtil::kAddr3},
       .should_not_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0,
                                        NCNLinuxMockedNetlinkTestUtil::kAddr2},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});
}

IN_PROC_BROWSER_TEST_F(AddressMapLinuxBrowserTest, CheckOnlineLinksDiffsApply) {
  // Delete the link.
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kNone);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links = {},
       .should_not_contain_links = {
           NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth}});

  // Add the link back.
  ncn_mocked_factory_->ncn_wrapper()->BufferAddLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  WaitForNetworkChange(ExpectedConnectionType::kConnected);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth},
       .should_not_contain_links = {}});

  // Delete link 1, add it back, delete it again. Also add link 2, delete it,
  // and add it back again.
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);

  ncn_mocked_factory_->ncn_wrapper()->BufferAddLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther);
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther);
  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  // Unconnected because kTestInterfaceOther has no addresses.
  WaitForNetworkChange(ExpectedConnectionType::kNone);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0},
       .should_not_contain_addresses = {},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther},
       .should_not_contain_links = {
           NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth}});
}

IN_PROC_BROWSER_TEST_F(AddressMapLinuxBrowserTest, CheckBothDiffsApply) {
  // Delete the link, add another.
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddLinkMsg(
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther);
  // Add some addresses
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr1,
      NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr2);
  ncn_mocked_factory_->ncn_wrapper()->BufferAddAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr3);
  // Delete some addresses.
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr2);
  ncn_mocked_factory_->ncn_wrapper()->BufferDeleteAddrMsg(
      NCNLinuxMockedNetlinkTestUtil::kAddr0);

  ncn_mocked_factory_->ncn_wrapper()->SendBuffer();

  // Connected because kAddr1 is associated with kTestInterfaceOther.
  WaitForNetworkChange(ExpectedConnectionType::kConnected);

  ExpectCorrectInfoInNetworkService(
      {.should_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr1,
                                    NCNLinuxMockedNetlinkTestUtil::kAddr3},
       .should_not_contain_addresses = {NCNLinuxMockedNetlinkTestUtil::kAddr0,
                                        NCNLinuxMockedNetlinkTestUtil::kAddr2},
       .should_contain_links =
           {NCNLinuxMockedNetlinkTestUtil::kTestInterfaceOther},
       .should_not_contain_links = {
           NCNLinuxMockedNetlinkTestUtil::kTestInterfaceEth}});
}

}  // namespace content