patchpanel: Extract patchpanel-client into its own subdir

Move patchpanel-client into its own package. By doing so,
patchpanel-util will be removed from patchpanel-client.
Systems that previously use patchpanel-util need to update
its build rule to use patchpanel-util.

This is done to avoid dependency loops (e.g. chromium:2359478).
Other system can depend on patchpanel-client instead of
patchpanel after this patch.

BUG=b:166193772
TEST=./build_packages --board=atlas;
TEST=FEATURES=test emerge-atlas patchpanel-client \
     patchpanel permission_broker system-proxy \
     vm_host_tools
TEST=/usr/libexec/fuzzers/patchpanel_client_fuzzer
TEST=tryjob --hwtest
TEST=tast run <DUT> platform.Firewall
TEST=Crostini and ARC running

Cq-Depend: chromium:2382997
Change-Id: I6244b4808c75a75b69b0276aa10489b1d2501025
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2384496
Tested-by: Jason Jeremy Iman <jasongustaman@chromium.org>
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
Reviewed-by: Yusuke Sato <yusukes@chromium.org>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Garrick Evans <garrick@chromium.org>
Commit-Queue: Jason Jeremy Iman <jasongustaman@chromium.org>
diff --git a/patchpanel/dbus/client.cc b/patchpanel/dbus/client.cc
new file mode 100644
index 0000000..8f55543
--- /dev/null
+++ b/patchpanel/dbus/client.cc
@@ -0,0 +1,523 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "patchpanel/dbus/client.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <chromeos/dbus/service_constants.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+
+#include "patchpanel/net_util.h"
+
+namespace patchpanel {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& stream,
+                         const ModifyPortRuleRequest& request) {
+  stream << "{ operation: "
+         << ModifyPortRuleRequest::Operation_Name(request.op())
+         << ", rule type: "
+         << ModifyPortRuleRequest::RuleType_Name(request.type())
+         << ", protocol: "
+         << ModifyPortRuleRequest::Protocol_Name(request.proto());
+  if (!request.input_ifname().empty()) {
+    stream << ", input interface name: " << request.input_ifname();
+  }
+  if (!request.input_dst_ip().empty()) {
+    stream << ", input destination IP: " << request.input_dst_ip();
+  }
+  stream << ", input destination port: " << request.input_dst_port();
+  if (!request.dst_ip().empty()) {
+    stream << ", destination IP: " << request.dst_ip();
+  }
+  if (request.dst_port() != 0) {
+    stream << ", destination port: " << request.dst_port();
+  }
+  stream << " }";
+  return stream;
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<Client> Client::New() {
+  dbus::Bus::Options opts;
+  opts.bus_type = dbus::Bus::SYSTEM;
+  scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(opts)));
+
+  if (!bus->Connect()) {
+    LOG(ERROR) << "Failed to connect to system bus";
+    return nullptr;
+  }
+
+  dbus::ObjectProxy* proxy = bus->GetObjectProxy(
+      kPatchPanelServiceName, dbus::ObjectPath(kPatchPanelServicePath));
+  if (!proxy) {
+    LOG(ERROR) << "Unable to get dbus proxy for " << kPatchPanelServiceName;
+    return nullptr;
+  }
+
+  return std::make_unique<Client>(std::move(bus), proxy);
+}
+
+Client::~Client() {
+  if (bus_)
+    bus_->ShutdownAndBlock();
+}
+
+bool Client::NotifyArcStartup(pid_t pid) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kArcStartupMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  ArcStartupRequest request;
+  request.set_pid(static_cast<uint32_t>(pid));
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ArcStartupRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  ArcStartupResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  return true;
+}
+
+bool Client::NotifyArcShutdown() {
+  dbus::MethodCall method_call(kPatchPanelInterface, kArcShutdownMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  ArcShutdownRequest request;
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ArcShutdownRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  ArcShutdownResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  return true;
+}
+
+std::vector<NetworkDevice> Client::NotifyArcVmStartup(uint32_t cid) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kArcVmStartupMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  ArcVmStartupRequest request;
+  request.set_cid(cid);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ArcVmStartupRequest proto";
+    return {};
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return {};
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  ArcVmStartupResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return {};
+  }
+
+  std::vector<NetworkDevice> devices;
+  for (const auto& d : response.devices()) {
+    devices.emplace_back(d);
+  }
+  return devices;
+}
+
+bool Client::NotifyArcVmShutdown(uint32_t cid) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kArcVmShutdownMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  ArcVmShutdownRequest request;
+  request.set_cid(cid);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ArcVmShutdownRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  ArcVmShutdownResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  return true;
+}
+
+bool Client::NotifyTerminaVmStartup(uint32_t cid,
+                                    NetworkDevice* device,
+                                    IPv4Subnet* container_subnet) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kTerminaVmStartupMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  TerminaVmStartupRequest request;
+  request.set_cid(cid);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode TerminaVmStartupRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  TerminaVmStartupResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  if (!response.has_device()) {
+    LOG(ERROR) << "No device found";
+    return false;
+  }
+  *device = response.device();
+
+  if (response.has_container_subnet()) {
+    *container_subnet = response.container_subnet();
+  } else {
+    LOG(WARNING) << "No container subnet found";
+  }
+
+  return true;
+}
+
+bool Client::NotifyTerminaVmShutdown(uint32_t cid) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kTerminaVmShutdownMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  TerminaVmShutdownRequest request;
+  request.set_cid(cid);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode TerminaVmShutdownRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  TerminaVmShutdownResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  return true;
+}
+
+bool Client::NotifyPluginVmStartup(uint64_t vm_id,
+                                   int subnet_index,
+                                   NetworkDevice* device) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kPluginVmStartupMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  PluginVmStartupRequest request;
+  request.set_id(vm_id);
+  request.set_subnet_index(subnet_index);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode PluginVmStartupRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  PluginVmStartupResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  if (!response.has_device()) {
+    LOG(ERROR) << "No device found";
+    return false;
+  }
+  *device = response.device();
+
+  return true;
+}
+
+bool Client::NotifyPluginVmShutdown(uint64_t vm_id) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kPluginVmShutdownMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  PluginVmShutdownRequest request;
+  request.set_id(vm_id);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode PluginVmShutdownRequest proto";
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send dbus message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  PluginVmShutdownResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse response proto";
+    return false;
+  }
+
+  return true;
+}
+
+bool Client::DefaultVpnRouting(int socket) {
+  return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::DEFAULT_ROUTING);
+}
+
+bool Client::RouteOnVpn(int socket) {
+  return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::ROUTE_ON_VPN);
+}
+
+bool Client::BypassVpn(int socket) {
+  return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::BYPASS_VPN);
+}
+
+bool Client::SendSetVpnIntentRequest(
+    int socket, SetVpnIntentRequest::VpnRoutingPolicy policy) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kSetVpnIntentMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  SetVpnIntentRequest request;
+  SetVpnIntentResponse response;
+  request.set_policy(policy);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode SetVpnIntentRequest proto";
+    return false;
+  }
+  writer.AppendFileDescriptor(socket);
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR)
+        << "Failed to send SetVpnIntentRequest message to patchpanel service";
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse SetVpnIntentResponse proto";
+    return false;
+  }
+
+  if (!response.success()) {
+    LOG(ERROR) << "SetVpnIntentRequest failed";
+    return false;
+  }
+  return true;
+}
+
+std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse>
+Client::ConnectNamespace(pid_t pid,
+                         const std::string& outbound_ifname,
+                         bool forward_user_traffic) {
+  // Prepare and serialize the request proto.
+  ConnectNamespaceRequest request;
+  request.set_pid(static_cast<int32_t>(pid));
+  request.set_outbound_physical_device(outbound_ifname);
+  request.set_allow_user_traffic(forward_user_traffic);
+
+  dbus::MethodCall method_call(kPatchPanelInterface, kConnectNamespaceMethod);
+  dbus::MessageWriter writer(&method_call);
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ConnectNamespaceRequest proto";
+    return {};
+  }
+
+  // Prepare an fd pair and append one fd directly after the serialized request.
+  int pipe_fds[2] = {-1, -1};
+  if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
+    PLOG(ERROR) << "Failed to create a pair of fds with pipe2()";
+    return {};
+  }
+  base::ScopedFD fd_local(pipe_fds[0]);
+  // MessageWriter::AppendFileDescriptor duplicates the fd, so use ScopeFD to
+  // make sure the original fd is closed eventually.
+  base::ScopedFD fd_remote(pipe_fds[1]);
+  writer.AppendFileDescriptor(pipe_fds[1]);
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send ConnectNamespace message to patchpanel";
+    return {};
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  ConnectNamespaceResponse response;
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse ConnectNamespaceResponse proto";
+    return {};
+  }
+
+  if (response.peer_ifname().empty() || response.host_ifname().empty()) {
+    LOG(ERROR) << "ConnectNamespace for netns pid " << pid << " failed";
+    return {};
+  }
+
+  std::string subnet_info = IPv4AddressToCidrString(
+      response.ipv4_subnet().base_addr(), response.ipv4_subnet().prefix_len());
+  LOG(INFO) << "ConnectNamespace for netns pid " << pid
+            << " succeeded: peer_ifname=" << response.peer_ifname()
+            << " peer_ipv4_address="
+            << IPv4AddressToString(response.peer_ipv4_address())
+            << " host_ifname=" << response.host_ifname()
+            << " host_ipv4_address="
+            << IPv4AddressToString(response.host_ipv4_address())
+            << " subnet=" << subnet_info;
+
+  return std::make_pair(std::move(fd_local), std::move(response));
+}
+
+std::vector<TrafficCounter> Client::GetTrafficCounters(
+    const std::set<std::string>& devices) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kGetTrafficCountersMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  TrafficCountersRequest request;
+  for (const auto& device : devices) {
+    request.add_devices(device);
+  }
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode TrafficCountersRequest proto";
+    return {};
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR) << "Failed to send TrafficCountersRequest message to patchpanel "
+                  "service";
+    return {};
+  }
+
+  TrafficCountersResponse response;
+  dbus::MessageReader reader(dbus_response.get());
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse TrafficCountersResponse proto";
+    return {};
+  }
+
+  return {response.counters().begin(), response.counters().end()};
+}
+
+bool Client::ModifyPortRule(ModifyPortRuleRequest::Operation op,
+                            ModifyPortRuleRequest::RuleType type,
+                            ModifyPortRuleRequest::Protocol proto,
+                            const std::string& input_ifname,
+                            const std::string& input_dst_ip,
+                            uint32_t input_dst_port,
+                            const std::string& dst_ip,
+                            uint32_t dst_port) {
+  dbus::MethodCall method_call(kPatchPanelInterface, kModifyPortRuleMethod);
+  dbus::MessageWriter writer(&method_call);
+
+  ModifyPortRuleRequest request;
+  ModifyPortRuleResponse response;
+
+  request.set_op(op);
+  request.set_type(type);
+  request.set_proto(proto);
+  request.set_input_ifname(input_ifname);
+  request.set_input_dst_ip(input_dst_ip);
+  request.set_input_dst_port(input_dst_port);
+  request.set_dst_ip(dst_ip);
+  request.set_dst_port(dst_port);
+
+  if (!writer.AppendProtoAsArrayOfBytes(request)) {
+    LOG(ERROR) << "Failed to encode ModifyPortRuleRequest proto " << request;
+    return false;
+  }
+
+  std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+  if (!dbus_response) {
+    LOG(ERROR)
+        << "Failed to send ModifyPortRuleRequest message to patchpanel service "
+        << request;
+    return false;
+  }
+
+  dbus::MessageReader reader(dbus_response.get());
+  if (!reader.PopArrayOfBytesAsProto(&response)) {
+    LOG(ERROR) << "Failed to parse ModifyPortRuleResponse proto " << request;
+    return false;
+  }
+
+  if (!response.success()) {
+    LOG(ERROR) << "ModifyPortRuleRequest failed " << request;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace patchpanel