chromium/chrome/browser/ash/crostini/crostini_port_forwarder.h

// 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.

#ifndef CHROME_BROWSER_ASH_CROSTINI_CROSTINI_PORT_FORWARDER_H_
#define CHROME_BROWSER_ASH_CROSTINI_CROSTINI_PORT_FORWARDER_H_

#include <string>
#include <unordered_map>

#include "base/files/scoped_file.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/values.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "components/keyed_service/core/keyed_service.h"

class Profile;

namespace crostini {

extern const char kDefaultInterfaceToForward[];
extern const char kWlanInterface[];
extern const char kPortNumberKey[];
extern const char kPortProtocolKey[];
extern const char kPortInterfaceKey[];
extern const char kPortLabelKey[];
extern const char kPortVmNameKey[];
extern const char kPortContainerNameKey[];

class CrostiniPortForwarder : public KeyedService {
 public:
  class Observer : public base::CheckedObserver {
   public:
    // Called when a port's active state changes.
    virtual void OnActivePortsChanged(const base::Value::List& activePorts) = 0;
    virtual void OnActiveNetworkChanged(const base::Value& interface,
                                        const base::Value& ipAddress) = 0;
  };

  enum class Protocol {
    TCP = 0,
    UDP = 1,
  };

  struct PortRuleKey {
    uint16_t port_number;
    Protocol protocol_type;
    guest_os::GuestId container_id;

    bool operator==(const PortRuleKey& other) const {
      return port_number == other.port_number &&
             protocol_type == other.protocol_type;
    }
  };

  // Helper for using PortRuleKey as key entries in std::unordered_maps.
  struct PortRuleKeyHasher {
    std::size_t operator()(const PortRuleKey& k) const {
      return ((std::hash<uint16_t>()(k.port_number) ^
               (std::hash<Protocol>()(k.protocol_type) << 1)) >>
              1);
    }
  };

  using ResultCallback = base::OnceCallback<void(bool)>;

  void AddObserver(Observer* observer) { observers_.AddObserver(observer); }

  void RemoveObserver(Observer* observer) {
    observers_.RemoveObserver(observer);
  }

  // The result_callback will only be called with success=true IF all conditions
  // pass. This means a port setting has been successfully updated in the
  // iptables and the profile preference setting has also been successfully
  // updated.
  void ActivatePort(const guest_os::GuestId& container_id,
                    uint16_t port_number,
                    const Protocol& protocol_type,
                    ResultCallback result_callback);
  void AddPort(const guest_os::GuestId& container_id,
               uint16_t port_number,
               const Protocol& protocol_type,
               const std::string& label,
               ResultCallback result_callback);
  void DeactivatePort(const guest_os::GuestId& container_id,
                      uint16_t port_number,
                      const Protocol& protocol_type,
                      ResultCallback result_callback);
  void RemovePort(const guest_os::GuestId& container_id,
                  uint16_t port_number,
                  const Protocol& protocol_type,
                  ResultCallback result_callback);

  // TODO(matterchen): For the two following methods, implement callback
  // results.

  // Deactivate all ports belonging to the container_id and removes them from
  // the preferences.
  void RemoveAllPorts(const guest_os::GuestId& container_id);

  // Deactivate all active ports belonging to the container_id and set their
  // preference to inactive such that these ports will not be automatically
  // re-forwarded on re-startup. This is called on container shutdown.
  void DeactivateAllActivePorts(const guest_os::GuestId& container_id);

  base::Value::List GetActivePorts();
  base::Value::List GetActiveNetworkInfo();

  size_t GetNumberOfForwardedPortsForTesting();
  std::optional<base::Value> ReadPortPreferenceForTesting(
      const PortRuleKey& key);
  void ActiveNetworksChanged(const std::string& interface,
                             const std::string& ip_address);

  static CrostiniPortForwarder* GetForProfile(Profile* profile);

  static void EnsureFactoryBuilt();

  explicit CrostiniPortForwarder(Profile* profile);

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

  ~CrostiniPortForwarder() override;

 private:
  FRIEND_TEST_ALL_PREFIXES(CrostiniPortForwarderTest,
                           TryActivatePortPermissionBrokerClientFail);
  FRIEND_TEST_ALL_PREFIXES(CrostiniPortForwarderTest, GetActivePortsForUI);

  void SignalActivePortsChanged();
  bool MatchPortRuleDict(const base::Value& dict, const PortRuleKey& key);
  bool MatchPortRuleContainerId(const base::Value& dict,
                                const guest_os::GuestId& container_id);
  void AddNewPortPreference(const PortRuleKey& key, const std::string& label);
  bool RemovePortPreference(const PortRuleKey& key);
  std::optional<base::Value> ReadPortPreference(const PortRuleKey& key);

  void OnActivatePortCompleted(ResultCallback result_callback,
                               PortRuleKey key,
                               bool success);
  void OnRemoveOrDeactivatePortCompleted(ResultCallback result_callback,
                                         PortRuleKey key,
                                         bool success);
  void TryDeactivatePort(const PortRuleKey& key,
                         const guest_os::GuestId& container_id,
                         base::OnceCallback<void(bool)> result_callback);
  void TryActivatePort(const PortRuleKey& key,
                       const guest_os::GuestId& container_id,
                       base::OnceCallback<void(bool)> result_callback);
  void UpdateActivePortInterfaces();

  // For each port rule (protocol, port, interface), keep track of the fd which
  // requested it so we can release it on removal / deactivate.
  std::unordered_map<PortRuleKey, base::ScopedFD, PortRuleKeyHasher>
      forwarded_ports_;

  // Current interface to forward ports on.
  std::string current_interface_;
  std::string ip_address_;

  base::ObserverList<Observer> observers_;

  raw_ptr<Profile> profile_;

  base::WeakPtrFactory<CrostiniPortForwarder> weak_ptr_factory_{this};

};  // class

}  // namespace crostini

#endif  // CHROME_BROWSER_ASH_CROSTINI_CROSTINI_PORT_FORWARDER_H_