chromium/chrome/browser/ash/crostini/crostini_port_forwarder_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/crostini/crostini_port_forwarder.h"

#include "base/test/test_future.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/dbus/permission_broker/fake_permission_broker_client.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Mock;
using testing::Return;

void TestingCallback(bool* out, base::OnceClosure closure, bool in) {
  *out = in;
  std::move(closure).Run();
}

namespace crostini {
using Protocol = CrostiniPortForwarder::Protocol;

class CrostiniPortForwarderTest : public testing::Test {
 public:
  CrostiniPortForwarderTest()
      : default_container_id_(DefaultContainerId()),
        other_container_id_(
            guest_os::GuestId(kCrostiniDefaultVmType, "other", "other")),
        inactive_container_id_(
            guest_os::GuestId(kCrostiniDefaultVmType, "inactive", "inactive")) {
    network_handler_helper_ = std::make_unique<ash::NetworkHandlerTestHelper>();
    network_handler_helper_->AddDefaultProfiles();
    network_handler_helper_->ResetDevicesAndServices();
  }

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

  ~CrostiniPortForwarderTest() override = default;

  void SetUp() override {
    ash::ChunneldClient::InitializeFake();
    ash::CiceroneClient::InitializeFake();
    ash::ConciergeClient::InitializeFake();
    ash::SeneschalClient::InitializeFake();
    chromeos::PermissionBrokerClient::InitializeFake();
    profile_ = std::make_unique<TestingProfile>();
    CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
        kCrostiniDefaultVmName);
    CrostiniManager::GetForProfile(profile())->AddRunningContainerForTesting(
        kCrostiniDefaultVmName,
        ContainerInfo(kCrostiniDefaultContainerName, kCrostiniDefaultUsername,
                      "home/testuser1", "CONTAINER_IP_ADDRESS"));
    test_helper_ = std::make_unique<CrostiniTestHelper>(profile_.get());
    crostini_port_forwarder_ =
        std::make_unique<CrostiniPortForwarder>(profile());
    crostini_port_forwarder_->AddObserver(&mock_observer_);
  }

  void TearDown() override {
    chromeos::PermissionBrokerClient::Shutdown();
    crostini_port_forwarder_->RemoveObserver(&mock_observer_);
    crostini_port_forwarder_.reset();
    test_helper_.reset();
    profile_.reset();
    ash::SeneschalClient::Shutdown();
    ash::ConciergeClient::Shutdown();
    ash::CiceroneClient::Shutdown();
    ash::ChunneldClient::Shutdown();
  }

 protected:
  class MockPortObserver : public CrostiniPortForwarder::Observer {
   public:
    MOCK_METHOD(void,
                OnActivePortsChanged,
                (const base::Value::List& activePorts),
                (override));
    MOCK_METHOD(void,
                OnActiveNetworkChanged,
                (const base::Value& interface, const base::Value& ipAddress),
                (override));
  };

  Profile* profile() { return profile_.get(); }

  CrostiniPortForwarder::PortRuleKey GetPortKey(
      int port_number,
      Protocol protocol_type,
      guest_os::GuestId container_id) {
    return {
        .port_number = static_cast<uint16_t>(port_number),
        .protocol_type = protocol_type,
        .container_id = container_id,
    };
  }

  void MakePermissionBrokerPortForwardingExpectation(int port_number,
                                                     Protocol protocol,
                                                     bool exists,
                                                     const char* interface) {
    switch (protocol) {
      case Protocol::TCP:
        EXPECT_EQ(
            chromeos::FakePermissionBrokerClient::Get()->HasTcpPortForward(
                port_number, interface),
            exists);
        break;
      case Protocol::UDP:
        EXPECT_EQ(
            chromeos::FakePermissionBrokerClient::Get()->HasUdpPortForward(
                port_number, interface),
            exists);
        break;
    }
  }

  void MakePortPreferenceExpectation(CrostiniPortForwarder::PortRuleKey key,
                                     bool exists,
                                     std::string label) {
    std::optional<base::Value> pref =
        crostini_port_forwarder_->ReadPortPreferenceForTesting(key);
    EXPECT_EQ(exists, pref.has_value());
    if (!exists) {
      return;
    }
    EXPECT_EQ(key.port_number,
              pref.value().GetDict().FindInt(crostini::kPortNumberKey).value());
    EXPECT_EQ(
        static_cast<int>(key.protocol_type),
        pref.value().GetDict().FindInt(crostini::kPortProtocolKey).value());
    EXPECT_EQ(key.container_id, guest_os::GuestId(pref.value()));
    EXPECT_EQ(label,
              *pref.value().GetDict().FindString(crostini::kPortLabelKey));
  }

  void MakePortExistenceExpectation(CrostiniPortForwarder::PortRuleKey port,
                                    std::string label,
                                    bool expected_pref,
                                    bool expected_permission) {
    MakePortPreferenceExpectation(port, /*exists=*/expected_pref,
                                  /*label=*/label);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/expected_permission,
        /*interface=*/crostini::kDefaultInterfaceToForward);
  }

  bool AddPortFromKey(CrostiniPortForwarder::PortRuleKey port) {
    base::test::TestFuture<bool> result_future;
    crostini_port_forwarder_->AddPort(port.container_id, port.port_number,
                                      port.protocol_type, "",
                                      result_future.GetCallback());
    return result_future.Get();
  }

  bool ActivatePortFromKey(CrostiniPortForwarder::PortRuleKey port) {
    base::test::TestFuture<bool> result_future;
    crostini_port_forwarder_->ActivatePort(port.container_id, port.port_number,
                                           port.protocol_type,
                                           result_future.GetCallback());
    return result_future.Get();
  }

  bool RemovePortFromKey(CrostiniPortForwarder::PortRuleKey port) {
    base::test::TestFuture<bool> result_future;
    crostini_port_forwarder_->RemovePort(port.container_id, port.port_number,
                                         port.protocol_type,
                                         result_future.GetCallback());
    return result_future.Get();
  }

  bool DeactivatePortFromKey(CrostiniPortForwarder::PortRuleKey port) {
    base::test::TestFuture<bool> result_future;
    crostini_port_forwarder_->DeactivatePort(
        port.container_id, port.port_number, port.protocol_type,
        result_future.GetCallback());
    return result_future.Get();
  }

  guest_os::GuestId default_container_id_;
  guest_os::GuestId other_container_id_;
  guest_os::GuestId inactive_container_id_;

  testing::NiceMock<MockPortObserver> mock_observer_;

  std::unique_ptr<ash::NetworkHandlerTestHelper> network_handler_helper_;
  std::unique_ptr<CrostiniTestHelper> test_helper_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<CrostiniPortForwarder> crostini_port_forwarder_;
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(CrostiniPortForwarderTest, AddPort) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged).Times(ports_to_add.size());

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Adding ports fails as they already exist.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_FALSE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);
}

TEST_F(CrostiniPortForwarderTest, RemovePort) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_remove = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> missing_ports_to_remove = {
      GetPortKey(5005, Protocol::TCP, default_container_id_),
      GetPortKey(5006, Protocol::UDP, default_container_id_)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_to_add.size() + ports_to_remove.size());

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Remove ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_remove) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_TRUE(RemovePortFromKey(port));
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            1U);

  // Removing ports fails due to them not existing in prefs.
  for (CrostiniPortForwarder::PortRuleKey& port : missing_ports_to_remove) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_FALSE(RemovePortFromKey(port));
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            1U);
}

TEST_F(CrostiniPortForwarderTest, DeactivatePort) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_deactivate = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> missing_ports_to_deactivate =
      {GetPortKey(5005, Protocol::TCP, default_container_id_),
       GetPortKey(5006, Protocol::UDP, default_container_id_)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_to_add.size() + ports_to_deactivate.size() * 2);

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Deactivate ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_deactivate) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_TRUE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            1U);

  // Deactivating ports fail due to the ports already being deactivated.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_deactivate) {
    MakePortExistenceExpectation(port, "", true, false);
    EXPECT_FALSE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            1U);

  // Deactivating ports fails due to the ports not existing in the prefs.
  for (CrostiniPortForwarder::PortRuleKey& port : missing_ports_to_deactivate) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_FALSE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            1U);
}

TEST_F(CrostiniPortForwarderTest, ActivatePort) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_deactivate = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_activate = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> missing_ports_to_activate = {
      GetPortKey(5005, Protocol::TCP, default_container_id_),
      GetPortKey(5006, Protocol::TCP, default_container_id_),
      GetPortKey(5007, Protocol::TCP, default_container_id_)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_to_add.size() + ports_to_deactivate.size() +
             ports_to_activate.size());

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Deactivate ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_deactivate) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_TRUE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Activate ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_activate) {
    MakePortExistenceExpectation(port, "", true, false);
    EXPECT_TRUE(ActivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            2U);

  // Activating ports fails due to ports already being active.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_activate) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_FALSE(ActivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            2U);

  // Activating ports fails due to missing prefs.
  for (CrostiniPortForwarder::PortRuleKey& port : missing_ports_to_activate) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_FALSE(ActivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            2U);
}

TEST_F(CrostiniPortForwarderTest, InactiveContainerHandling) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_for_inactive_container =
      {GetPortKey(5000, Protocol::TCP, inactive_container_id_),
       GetPortKey(5000, Protocol::UDP, inactive_container_id_),
       GetPortKey(5001, Protocol::UDP, inactive_container_id_)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_for_inactive_container.size() * 4);

  // Add ports, fails due to an inactive container.
  for (CrostiniPortForwarder::PortRuleKey& port :
       ports_for_inactive_container) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_FALSE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Activate ports, fails due to an inactive container.
  for (CrostiniPortForwarder::PortRuleKey& port :
       ports_for_inactive_container) {
    MakePortExistenceExpectation(port, "", true, false);
    EXPECT_FALSE(ActivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Deactivate ports, fails due to an inactive container.
  for (CrostiniPortForwarder::PortRuleKey& port :
       ports_for_inactive_container) {
    MakePortExistenceExpectation(port, "", true, false);
    EXPECT_FALSE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Remove ports, fails due to an inactive container.
  for (CrostiniPortForwarder::PortRuleKey& port :
       ports_for_inactive_container) {
    MakePortExistenceExpectation(port, "", true, false);
    EXPECT_FALSE(RemovePortFromKey(port));
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);
}

TEST_F(CrostiniPortForwarderTest, DeactivateAllPorts) {
  guest_os::GuestId container_id = default_container_id_;
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, container_id),
      GetPortKey(5000, Protocol::UDP, container_id),
      GetPortKey(5001, Protocol::UDP, container_id)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_to_add.size() + 2);

  crostini_port_forwarder_->DeactivateAllActivePorts(container_id);
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Deactivate all ports.
  crostini_port_forwarder_->DeactivateAllActivePorts(container_id);
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);
}

TEST_F(CrostiniPortForwarderTest, RemoveAllPorts) {
  guest_os::GuestId container_id = default_container_id_;
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, container_id),
      GetPortKey(5000, Protocol::UDP, container_id),
      GetPortKey(5001, Protocol::UDP, container_id)};
  EXPECT_CALL(mock_observer_, OnActivePortsChanged)
      .Times(ports_to_add.size() + 2);

  // Remove all ports (ensuring that things don't break when there are
  // no ports to remove).
  crostini_port_forwarder_->RemoveAllPorts(container_id);
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Remove all ports.
  crostini_port_forwarder_->RemoveAllPorts(container_id);
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);
}

TEST_F(CrostiniPortForwarderTest, GetActivePorts) {
  guest_os::GuestId container_id = default_container_id_;
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, container_id),
      GetPortKey(5000, Protocol::UDP, container_id),
      GetPortKey(5001, Protocol::UDP, container_id)};

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Get active ports.
  base::Value::List forwarded_ports =
      crostini_port_forwarder_->GetActivePorts();
  EXPECT_EQ(forwarded_ports.size(), ports_to_add.size());
  for (unsigned int i = 0; i < ports_to_add.size(); i++) {
    unsigned int reverse_index = ports_to_add.size() - i - 1;
    EXPECT_EQ(*(forwarded_ports[i].GetDict().Find("port_number")),
              base::Value(ports_to_add.at(reverse_index).port_number));
    EXPECT_EQ(*(forwarded_ports[i].GetDict().Find("protocol_type")),
              base::Value(static_cast<int>(
                  ports_to_add.at(reverse_index).protocol_type)));
  }
}

TEST_F(CrostiniPortForwarderTest, ActiveNetworksChanged) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  const char eth_interface[] = "eth0";
  EXPECT_CALL(mock_observer_, OnActivePortsChanged).Times(ports_to_add.size());
  EXPECT_CALL(mock_observer_, OnActiveNetworkChanged).Times(2);

  // Add ports
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortPreferenceExpectation(port, /*exists=*/true,
                                  /*label=*/"");
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/crostini::kDefaultInterfaceToForward);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/false, /*interface=*/eth_interface);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Request to update interface to kDefaultInterfaceToForward, no change
  // required as ports are already being forwarded on kDefaultInterfaceToForward
  // by default.
  crostini_port_forwarder_->ActiveNetworksChanged(
      crostini::kDefaultInterfaceToForward, "127.0.0.1");
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortPreferenceExpectation(port, /*exists=*/true,
                                  /*label=*/"");
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/crostini::kDefaultInterfaceToForward);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/false, /*interface=*/eth_interface);
  }

  // Request to update interface to "", invalid request, no change required.
  crostini_port_forwarder_->ActiveNetworksChanged("", "");
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortPreferenceExpectation(port, /*exists=*/true,
                                  /*label=*/"");
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/crostini::kDefaultInterfaceToForward);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/false, /*interface=*/eth_interface);
  }

  // Request to update interface to eth_interface, ports are updated to use
  // the eth_interface and no longer use what they were using before
  // (kDefaultInterfaceToForward).
  crostini_port_forwarder_->ActiveNetworksChanged(eth_interface, "10.1.1.1");
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortPreferenceExpectation(port, /*exists=*/true,
                                  /*label=*/"");
    // Deactivating forwarding on the previous interface is handled in Chromeos
    // and by the lifelines used to track port rules. Until the port is released
    // in Chromeos, both interfaces will be used.
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/crostini::kDefaultInterfaceToForward);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/eth_interface);
  }

  // Request to update interface to kDefaultInterfaceToForward with an IPv6
  // address. Needs a new interface because this is only called when interfaces
  // changes.
  crostini_port_forwarder_->ActiveNetworksChanged(kDefaultInterfaceToForward,
                                                  "2001:db8:0:1");
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortPreferenceExpectation(port, /*exists=*/true,
                                  /*label=*/"");
    // Deactivating forwarding on the previous interface is handled in Chromeos
    // and by the lifelines used to track port rules. Until the port is released
    // in Chromeos, both interfaces will be used.
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/crostini::kDefaultInterfaceToForward);
    MakePermissionBrokerPortForwardingExpectation(
        /*port_number=*/port.port_number, /*protocol=*/port.protocol_type,
        /*exists=*/true, /*interface=*/eth_interface);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);
}

TEST_F(CrostiniPortForwarderTest, HandlingOfflinePermissionBroker) {
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_add = {
      GetPortKey(5000, Protocol::TCP, default_container_id_),
      GetPortKey(5000, Protocol::UDP, default_container_id_),
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  std::vector<CrostiniPortForwarder::PortRuleKey> ports_to_deactivate = {
      GetPortKey(5001, Protocol::UDP, default_container_id_)};
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Add ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    MakePortExistenceExpectation(port, "", false, false);
    EXPECT_TRUE(AddPortFromKey(port));
    MakePortExistenceExpectation(port, "", true, true);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            3U);

  // Deactivate ports.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_deactivate) {
    MakePortExistenceExpectation(port, "", true, true);
    EXPECT_TRUE(DeactivatePortFromKey(port));
    MakePortExistenceExpectation(port, "", true, false);
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            2U);

  // Shut PermissionBrokerClient down.
  chromeos::PermissionBrokerClient::Shutdown();

  // Activating ports fails, due to permission broker being inaccessible.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    EXPECT_FALSE(ActivatePortFromKey(port));
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            2U);

  // Deactivating ports fails, due to permission broker being inaccessible.
  for (CrostiniPortForwarder::PortRuleKey& port : ports_to_add) {
    EXPECT_FALSE(DeactivatePortFromKey(port));
  }
  EXPECT_EQ(crostini_port_forwarder_->GetNumberOfForwardedPortsForTesting(),
            0U);

  // Re-initialize otherwise Shutdown in TearDown phase will break.
  chromeos::PermissionBrokerClient::InitializeFake();
}
}  // namespace crostini