patchpanel: Add support for getting all managed devices.

Adds a new dbus method - GetDevices - and the supporting implementation
in the arc and crostini services to acquire the list of devices
currently managed by patchpanel. This currently includes only the
virtual devices it creates for guests and excludes physical and virtual
(like VPN) devices tracked by shill.

BUG=b:174432555
TEST=units

Change-Id: I3e62c89836bf7bfbc0fade5260ea27eb1f60df9f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2566852
Tested-by: Garrick Evans <garrick@chromium.org>
Commit-Queue: Garrick Evans <garrick@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
diff --git a/patchpanel/arc_service.cc b/patchpanel/arc_service.cc
index 60ee512..34311fa 100644
--- a/patchpanel/arc_service.cc
+++ b/patchpanel/arc_service.cc
@@ -587,4 +587,11 @@
 
   return configs;
 }
+
+void ArcService::ScanDevices(
+    base::RepeatingCallback<void(const Device&)> callback) const {
+  for (const auto& [_, d] : devices_)
+    callback.Run(*d.get());
+}
+
 }  // namespace patchpanel
diff --git a/patchpanel/arc_service.h b/patchpanel/arc_service.h
index 0e81a76..0e40bde 100644
--- a/patchpanel/arc_service.h
+++ b/patchpanel/arc_service.h
@@ -52,6 +52,10 @@
   // configurations, if any, are currently associated to TAP devices.
   std::vector<const Device::Config*> GetDeviceConfigs() const;
 
+  // Walks the current list of devices managed by the service invoking the
+  // callback for each, allowing for safe inspection/evaluation.
+  void ScanDevices(base::RepeatingCallback<void(const Device&)> callback) const;
+
   // Callback from ShillClient, invoked whenever the device list changes.
   // |shill_devices_| will contain all devices currently connected to shill
   // (e.g. "eth0", "wlan0", etc).
diff --git a/patchpanel/arc_service_test.cc b/patchpanel/arc_service_test.cc
index 676233e..e8a267d 100644
--- a/patchpanel/arc_service_test.cc
+++ b/patchpanel/arc_service_test.cc
@@ -28,6 +28,7 @@
 using testing::Return;
 using testing::ReturnRef;
 using testing::StrEq;
+using testing::UnorderedElementsAre;
 
 namespace patchpanel {
 namespace {
@@ -268,6 +269,29 @@
   svc->OnDevicesChanged({"eth0"}, {});
 }
 
+TEST_F(ArcServiceTest, ContainerImpl_ScanDevices) {
+  EXPECT_CALL(*datapath_, NetnsAttachName(_, _)).WillRepeatedly(Return(true));
+  EXPECT_CALL(*datapath_, ConnectVethPair(_, _, _, _, _, _, _, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*datapath_, AddBridge(_, _, _)).WillRepeatedly(Return(true));
+  EXPECT_CALL(*datapath_, AddToBridge(_, _)).WillRepeatedly(Return(true));
+
+  auto svc = NewService(GuestMessage::ARC);
+  svc->Start(kTestPID);
+  svc->OnDevicesChanged({"eth0", "wlan0"}, {});
+
+  std::vector<std::string> devs;
+  svc->ScanDevices(base::BindRepeating(
+      [](std::vector<std::string>* list, const Device& device) {
+        list->push_back(device.host_ifname());
+      },
+      &devs));
+
+  EXPECT_EQ(devs.size(), 2);
+  EXPECT_THAT(devs,
+              UnorderedElementsAre(StrEq("arc_eth0"), StrEq("arc_wlan0")));
+}
+
 TEST_F(ArcServiceTest, ContainerImpl_StartAfterDevice) {
   EXPECT_CALL(*datapath_, NetnsAttachName(StrEq("arc_netns"), kTestPID))
       .WillOnce(Return(true));
@@ -533,4 +557,32 @@
   svc->OnDevicesChanged({}, {"eth0"});
 }
 
+TEST_F(ArcServiceTest, VmImpl_ScanDevices) {
+  // Expectations for tap devices pre-creation.
+  EXPECT_CALL(*datapath_, AddTAP(StrEq(""), _, nullptr, StrEq("crosvm")))
+      .WillOnce(Return("vmtap0"))
+      .WillOnce(Return("vmtap1"))
+      .WillOnce(Return("vmtap2"))
+      .WillOnce(Return("vmtap3"))
+      .WillOnce(Return("vmtap4"))
+      .WillOnce(Return("vmtap5"));
+  EXPECT_CALL(*datapath_, AddBridge(_, _, _)).WillRepeatedly(Return(true));
+  EXPECT_CALL(*datapath_, AddToBridge(_, _)).WillRepeatedly(Return(true));
+
+  auto svc = NewService(GuestMessage::ARC_VM);
+  svc->Start(kTestPID);
+  svc->OnDevicesChanged({"eth0", "wlan0", "eth1"}, {});
+
+  std::vector<std::string> devs;
+  svc->ScanDevices(base::BindRepeating(
+      [](std::vector<std::string>* list, const Device& device) {
+        list->push_back(device.host_ifname());
+      },
+      &devs));
+
+  EXPECT_EQ(devs.size(), 3);
+  EXPECT_THAT(devs, UnorderedElementsAre(StrEq("arc_eth0"), StrEq("arc_wlan0"),
+                                         StrEq("arc_eth1")));
+}
+
 }  // namespace patchpanel
diff --git a/patchpanel/crostini_service.cc b/patchpanel/crostini_service.cc
index 77d7ea1..92ecee87 100644
--- a/patchpanel/crostini_service.cc
+++ b/patchpanel/crostini_service.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include "base/threading/thread_task_runner_handle.h"
@@ -33,6 +34,18 @@
                             base::NumberToString(vm_id).c_str());
 }
 
+bool ParseKey(const std::string& key, uint64_t* vm_id, bool* is_termina) {
+  auto s =
+      base::SplitString(key, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (s.size() != 2 || !base::StringToUint64(s[1], vm_id) || s[0] != "t" ||
+      s[0] != "p") {
+    LOG(DFATAL) << "Invalid key: " + key;
+    return false;
+  }
+  *is_termina = s[0] == "t";
+  return true;
+}
+
 bool IsEthernetOrWifiDevice(const ShillClient::Device& device) {
   return device.type == ShillClient::Device::Type::kEthernet ||
          device.type == ShillClient::Device::Type::kWifi;
@@ -134,6 +147,17 @@
   return it->second.get();
 }
 
+void CrostiniService::ScanDevices(
+    base::RepeatingCallback<void(uint64_t, bool, const Device&)> callback)
+    const {
+  for (const auto& [key, dev] : taps_) {
+    uint64_t vm_id;
+    bool is_termina;
+    if (ParseKey(key, &vm_id, &is_termina))
+      callback.Run(vm_id, is_termina, *dev.get());
+  }
+}
+
 std::unique_ptr<Device> CrostiniService::AddTAP(bool is_termina,
                                                 int subnet_index) {
   auto ipv4_subnet = addr_mgr_->AllocateIPv4Subnet(
diff --git a/patchpanel/crostini_service.h b/patchpanel/crostini_service.h
index 071ce70..0a2be52 100644
--- a/patchpanel/crostini_service.h
+++ b/patchpanel/crostini_service.h
@@ -39,6 +39,13 @@
 
   const Device* const TAP(uint64_t vm_id, bool is_termina) const;
 
+  // Walks the current list of devices managed by the service invoking the
+  // callback for each, allowing for safe inspection/evaluation.
+  // The first two callback args correspond to the vm_id and is_termina values
+  // originally provided to the TAP() function that created the device.
+  void ScanDevices(base::RepeatingCallback<void(uint64_t, bool, const Device&)>
+                       callback) const;
+
  private:
   std::unique_ptr<Device> AddTAP(bool is_termina, int subnet_index);
   void OnDefaultDeviceChanged(const ShillClient::Device& new_device,
diff --git a/patchpanel/manager.cc b/patchpanel/manager.cc
index ea55ce1..4f7a7c0 100644
--- a/patchpanel/manager.cc
+++ b/patchpanel/manager.cc
@@ -196,6 +196,7 @@
       {patchpanel::kConnectNamespaceMethod, &Manager::OnConnectNamespace},
       {patchpanel::kGetTrafficCountersMethod, &Manager::OnGetTrafficCounters},
       {patchpanel::kModifyPortRuleMethod, &Manager::OnModifyPortRule},
+      {patchpanel::kGetDevicesMethod, &Manager::OnGetDevices},
   };
 
   for (const auto& kv : kServiceMethods) {
@@ -397,6 +398,60 @@
   cros_svc_->Stop(vm_id, vm_type == GuestMessage::TERMINA_VM);
 }
 
+std::unique_ptr<dbus::Response> Manager::OnGetDevices(
+    dbus::MethodCall* method_call) {
+  std::unique_ptr<dbus::Response> dbus_response(
+      dbus::Response::FromMethodCall(method_call));
+
+  dbus::MessageReader reader(method_call);
+  dbus::MessageWriter writer(dbus_response.get());
+
+  patchpanel::GetDevicesRequest request;
+  patchpanel::GetDevicesResponse response;
+
+  if (!reader.PopArrayOfBytesAsProto(&request)) {
+    LOG(ERROR) << "Unable to parse request";
+    writer.AppendProtoAsArrayOfBytes(response);
+    return dbus_response;
+  }
+
+  static const auto arc_guest_type =
+      USE_ARCVM ? NetworkDevice::ARCVM : NetworkDevice::ARC;
+
+  arc_svc_->ScanDevices(base::BindRepeating(
+      [](patchpanel::GetDevicesResponse* resp, const Device& device) {
+        auto* dev = resp->add_devices();
+        dev->set_ifname(device.host_ifname());
+        dev->set_ipv4_addr(device.config().guest_ipv4_addr());
+        dev->set_guest_type(arc_guest_type);
+        if (const auto* subnet = device.config().ipv4_subnet()) {
+          auto* sub = dev->mutable_ipv4_subnet();
+          sub->set_base_addr(subnet->BaseAddress());
+          sub->set_prefix_len(subnet->PrefixLength());
+        }
+      },
+      &response));
+
+  cros_svc_->ScanDevices(base::BindRepeating(
+      [](patchpanel::GetDevicesResponse* resp, uint64_t vm_id, bool is_termina,
+         const Device& device) {
+        auto* dev = resp->add_devices();
+        dev->set_ifname(device.host_ifname());
+        dev->set_ipv4_addr(device.config().guest_ipv4_addr());
+        dev->set_guest_type(is_termina ? NetworkDevice::TERMINA_VM
+                                       : NetworkDevice::PLUGIN_VM);
+        if (const auto* subnet = device.config().ipv4_subnet()) {
+          auto* sub = dev->mutable_ipv4_subnet();
+          sub->set_base_addr(subnet->BaseAddress());
+          sub->set_prefix_len(subnet->PrefixLength());
+        }
+      },
+      &response));
+
+  writer.AppendProtoAsArrayOfBytes(response);
+  return dbus_response;
+}
+
 std::unique_ptr<dbus::Response> Manager::OnArcStartup(
     dbus::MethodCall* method_call) {
   LOG(INFO) << "ARC++ starting up";
diff --git a/patchpanel/manager.h b/patchpanel/manager.h
index 5055cda..3280772 100644
--- a/patchpanel/manager.h
+++ b/patchpanel/manager.h
@@ -116,6 +116,9 @@
   // address to guest-facing interface.
   void OnNDProxyMessage(const NDProxyMessage& msg);
 
+  // Handles DBus request for managed device list.
+  std::unique_ptr<dbus::Response> OnGetDevices(dbus::MethodCall* method_call);
+
   // Handles DBus notification indicating ARC++ is booting up.
   std::unique_ptr<dbus::Response> OnArcStartup(dbus::MethodCall* method_call);