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/BUILD.gn b/patchpanel/dbus/BUILD.gn
new file mode 100644
index 0000000..1ee5077
--- /dev/null
+++ b/patchpanel/dbus/BUILD.gn
@@ -0,0 +1,97 @@
+# Copyright 2020 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.
+
+import("//common-mk/pkg_config.gni")
+import("//common-mk/proto_library.gni")
+
+group("all") {
+ deps = [ ":libpatchpanel-client" ]
+ if (use.fuzzer) {
+ deps += [ ":patchpanel_client_fuzzer" ]
+ }
+ if (use.test) {
+ deps += [ ":patchpanel-client_testrunner" ]
+ }
+}
+
+pkg_config("target_defaults") {
+ pkg_deps = [
+ "libbrillo",
+ "libchrome-${libbase_ver}",
+ "system_api",
+ ]
+ if (use.fuzzer) {
+ pkg_deps += [ "protobuf" ]
+ } else {
+ pkg_deps += [ "protobuf-lite" ]
+ }
+ defines = [ "USE_ARCVM=${use.arcvm}" ]
+}
+
+proto_library("protos") {
+ configs = [ ":target_defaults" ]
+ proto_in_dir = ".."
+ proto_out_dir = "include/patchpanel"
+ sources = [ "${proto_in_dir}/ipc.proto" ]
+}
+
+libpatchpanel_client_sources = [
+ "../net_util.cc",
+ "client.cc",
+]
+
+static_library("libpatchpanel-client_test") {
+ configs += [ ":target_defaults" ]
+ all_dependent_pkg_deps = [
+ "protobuf",
+ "system_api",
+ ]
+ sources = libpatchpanel_client_sources
+ deps = [ ":protos" ]
+}
+
+shared_library("libpatchpanel-client") {
+ configs += [ ":target_defaults" ]
+ all_dependent_pkg_deps = [
+ "protobuf",
+ "system_api",
+ ]
+ sources = libpatchpanel_client_sources
+}
+
+if (use.fuzzer) {
+ pkg_config("fuzzing_config") {
+ pkg_deps = [ "libchrome-test-${libbase_ver}" ]
+ }
+
+ executable("patchpanel_client_fuzzer") {
+ configs += [
+ "//common-mk/common_fuzzer",
+ ":target_defaults",
+ ":fuzzing_config",
+ ]
+ sources = [ "client_fuzzer.cc" ]
+ deps = [ ":libpatchpanel-client" ]
+ }
+}
+
+if (use.test) {
+ pkg_config("test_config") {
+ pkg_deps = [ "libchrome-test-${libbase_ver}" ]
+ }
+
+ executable("patchpanel-client_testrunner") {
+ sources = [ "client_test.cc" ]
+ configs += [
+ "//common-mk:test",
+ ":target_defaults",
+ ":test_config",
+ ]
+ defines = [ "UNIT_TEST" ]
+ deps = [
+ ":libpatchpanel-client_test",
+ "//common-mk/testrunner",
+ ]
+ }
+}
diff --git a/patchpanel/dbus/OWNERS b/patchpanel/dbus/OWNERS
new file mode 100644
index 0000000..c9cce26
--- /dev/null
+++ b/patchpanel/dbus/OWNERS
@@ -0,0 +1 @@
+include /patchpanel/OWNERS
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
diff --git a/patchpanel/dbus/client.h b/patchpanel/dbus/client.h
new file mode 100644
index 0000000..cefeddc
--- /dev/null
+++ b/patchpanel/dbus/client.h
@@ -0,0 +1,102 @@
+// Copyright 2019 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.
+
+#ifndef PATCHPANEL_DBUS_CLIENT_H_
+#define PATCHPANEL_DBUS_CLIENT_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/scoped_file.h"
+#include <brillo/brillo_export.h>
+#include <dbus/bus.h>
+#include <dbus/object_proxy.h>
+#include <patchpanel/proto_bindings/patchpanel_service.pb.h>
+
+namespace patchpanel {
+
+// Simple wrapper around patchpanel DBus API. All public functions are blocking
+// DBus calls to patchpaneld. The method names and protobuf schema used by
+// patchpanel DBus API are defined in platform2/system_api/dbus/patchpanel.
+// Access control for clients is defined in platform2/patchpanel/dbus.
+class BRILLO_EXPORT Client {
+ public:
+ static std::unique_ptr<Client> New();
+
+ Client(const scoped_refptr<dbus::Bus>& bus, dbus::ObjectProxy* proxy)
+ : bus_(std::move(bus)), proxy_(proxy) {}
+ ~Client();
+
+ bool NotifyArcStartup(pid_t pid);
+ bool NotifyArcShutdown();
+
+ std::vector<NetworkDevice> NotifyArcVmStartup(uint32_t cid);
+ bool NotifyArcVmShutdown(uint32_t cid);
+
+ bool NotifyTerminaVmStartup(uint32_t cid,
+ NetworkDevice* device,
+ IPv4Subnet* container_subnet);
+ bool NotifyTerminaVmShutdown(uint32_t cid);
+
+ bool NotifyPluginVmStartup(uint64_t vm_id,
+ int subnet_index,
+ NetworkDevice* device);
+ bool NotifyPluginVmShutdown(uint64_t vm_id);
+
+ // Reset the VPN routing intent mark on a socket to the default policy for
+ // the current uid. This is in general incorrect to call this method for
+ // a socket that is already connected.
+ bool DefaultVpnRouting(int socket);
+
+ // Mark a socket to be always routed through a VPN if there is one.
+ // Must be called before the socket is connected.
+ bool RouteOnVpn(int socket);
+
+ // Mark a socket to be always routed through the physical network.
+ // Must be called before the socket is connected.
+ bool BypassVpn(int socket);
+
+ // Sends a ConnectNamespaceRequest for the given namespace pid. Returns a
+ // pair with a valid ScopedFD and the ConnectNamespaceResponse proto message
+ // received if the request succeeded. Closing the ScopedFD will teardown the
+ // veth and routing setup and free the allocated IPv4 subnet.
+ std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse>
+ ConnectNamespace(pid_t pid,
+ const std::string& outbound_ifname,
+ bool forward_user_traffic);
+
+ // Gets the traffic counters kept by patchpanel. |devices| is the set of
+ // interfaces (shill devices) for which counters should be returned, any
+ // unknown interfaces will be ignored. If |devices| is empty, counters for all
+ // known interfaces will be returned.
+ std::vector<TrafficCounter> GetTrafficCounters(
+ const std::set<std::string>& devices);
+
+ // Sends a ModifyPortRuleRequest to modify iptables ingress rules.
+ // This should only be called by permission_broker's 'devbroker'.
+ bool ModifyPortRule(patchpanel::ModifyPortRuleRequest::Operation op,
+ patchpanel::ModifyPortRuleRequest::RuleType type,
+ patchpanel::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);
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ObjectProxy* proxy_ = nullptr; // owned by bus_
+
+ bool SendSetVpnIntentRequest(int socket,
+ SetVpnIntentRequest::VpnRoutingPolicy policy);
+
+ DISALLOW_COPY_AND_ASSIGN(Client);
+};
+
+} // namespace patchpanel
+
+#endif // PATCHPANEL_DBUS_CLIENT_H_
diff --git a/patchpanel/dbus/client_fuzzer.cc b/patchpanel/dbus/client_fuzzer.cc
new file mode 100644
index 0000000..75a328f
--- /dev/null
+++ b/patchpanel/dbus/client_fuzzer.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 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 <net/if.h>
+
+#include <base/logging.h>
+#include <dbus/message.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "patchpanel/dbus/client.h"
+
+namespace patchpanel {
+
+class Environment {
+ public:
+ Environment() {
+ logging::SetMinLogLevel(logging::LOG_FATAL); // <- DISABLE LOGGING.
+ }
+};
+
+class FakeObjectProxy : public dbus::ObjectProxy {
+ public:
+ explicit FakeObjectProxy(dbus::Bus* bus)
+ : dbus::ObjectProxy(bus, "svc", dbus::ObjectPath("/obj/path"), 0) {}
+
+ std::unique_ptr<dbus::Response> CallMethodAndBlockWithErrorDetails(
+ dbus::MethodCall* method_call,
+ int timeout_ms,
+ dbus::ScopedDBusError* error) override {
+ return nullptr;
+ }
+
+ std::unique_ptr<dbus::Response> CallMethodAndBlock(
+ dbus::MethodCall* method_call, int timeout_ms) override {
+ return nullptr;
+ }
+
+ void CallMethod(dbus::MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback) override {}
+
+ void CallMethodWithErrorResponse(dbus::MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback callback) override {}
+
+ void CallMethodWithErrorCallback(dbus::MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback,
+ ErrorCallback error_callback) override {}
+
+ void ConnectToSignal(const std::string& interface_name,
+ const std::string& signal_name,
+ SignalCallback signal_callback,
+ OnConnectedCallback on_connected_callback) override {}
+
+ void WaitForServiceToBeAvailable(
+ WaitForServiceToBeAvailableCallback callback) override {}
+
+ void Detach() override {}
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static Environment env;
+ dbus::Bus::Options options;
+ scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
+ scoped_refptr<dbus::ObjectProxy> proxy(new FakeObjectProxy(bus.get()));
+ Client client(bus, proxy.get());
+ FuzzedDataProvider provider(data, size);
+
+ while (provider.remaining_bytes() > 0) {
+ client.NotifyArcStartup(provider.ConsumeIntegral<pid_t>());
+ client.NotifyArcVmStartup(provider.ConsumeIntegral<uint32_t>());
+ client.NotifyArcVmShutdown(provider.ConsumeIntegral<uint32_t>());
+ NetworkDevice device;
+ device.set_ifname(provider.ConsumeRandomLengthString(IFNAMSIZ * 2));
+ device.set_ipv4_addr(provider.ConsumeIntegral<uint32_t>());
+ device.mutable_ipv4_subnet()->set_base_addr(
+ provider.ConsumeIntegral<uint32_t>());
+ device.mutable_ipv4_subnet()->set_prefix_len(
+ provider.ConsumeIntegral<uint32_t>());
+ IPv4Subnet subnet;
+ subnet.set_base_addr(provider.ConsumeIntegral<uint32_t>());
+ subnet.set_prefix_len(provider.ConsumeIntegral<uint32_t>());
+ client.NotifyTerminaVmStartup(provider.ConsumeIntegral<uint32_t>(), &device,
+ &subnet);
+ client.NotifyTerminaVmShutdown(provider.ConsumeIntegral<uint32_t>());
+ client.NotifyPluginVmStartup(provider.ConsumeIntegral<uint64_t>(),
+ provider.ConsumeIntegral<int>(), &device);
+ client.NotifyPluginVmShutdown(provider.ConsumeIntegral<uint64_t>());
+ // TODO(garrick): Enable the following once the memory leaks in Chrome OS
+ // DBus are resolved.
+ // client.DefaultVpnRouting(provider.ConsumeIntegral<int>());
+ // client.RouteOnVpn(provider.ConsumeIntegral<int>());
+ // client.BypassVpn(provider.ConsumeIntegral<int>());
+ client.ConnectNamespace(provider.ConsumeIntegral<pid_t>(),
+ provider.ConsumeRandomLengthString(100),
+ provider.ConsumeBool());
+ std::set<std::string> devices_for_counters;
+ for (int i = 0; i < 10; i++) {
+ if (provider.ConsumeBool()) {
+ devices_for_counters.insert(
+ provider.ConsumeRandomLengthString(IFNAMSIZ * 2));
+ }
+ }
+ client.GetTrafficCounters(devices_for_counters);
+ }
+ bus->ShutdownAndBlock();
+ return 0;
+}
+
+} // namespace patchpanel
diff --git a/patchpanel/dbus/client_test.cc b/patchpanel/dbus/client_test.cc
new file mode 100644
index 0000000..01c7b30
--- /dev/null
+++ b/patchpanel/dbus/client_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2020 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 <chromeos/dbus/service_constants.h>
+#include <dbus/message.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_object_proxy.h>
+#include <dbus/object_path.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "patchpanel/net_util.h"
+
+namespace patchpanel {
+
+using ::testing::_;
+using ::testing::ByMove;
+using ::testing::Return;
+
+namespace {
+
+scoped_refptr<dbus::MockBus> MockDBus() {
+ return new dbus::MockBus{dbus::Bus::Options{}};
+}
+
+scoped_refptr<dbus::MockObjectProxy> PatchPanelMockProxy(dbus::MockBus* dbus) {
+ return new dbus::MockObjectProxy(dbus, kPatchPanelServiceName,
+ dbus::ObjectPath(kPatchPanelServicePath));
+}
+
+} // namespace
+
+TEST(ClientTest, ConnectNamespace) {
+ auto dbus = MockDBus();
+ auto proxy = PatchPanelMockProxy(dbus.get());
+ pid_t pid = 3456;
+ std::string outboud_ifname = "";
+
+ Client client(dbus, proxy.get());
+
+ // Failure case
+ auto result = client.ConnectNamespace(pid, outboud_ifname, false);
+ EXPECT_FALSE(result.first.is_valid());
+ EXPECT_TRUE(result.second.peer_ifname().empty());
+ EXPECT_TRUE(result.second.host_ifname().empty());
+ EXPECT_EQ(0, result.second.peer_ipv4_address());
+ EXPECT_EQ(0, result.second.host_ipv4_address());
+ EXPECT_EQ(0, result.second.ipv4_subnet().base_addr());
+ EXPECT_EQ(0, result.second.ipv4_subnet().prefix_len());
+
+ // Success case
+ patchpanel::ConnectNamespaceResponse response_proto;
+ response_proto.set_peer_ifname("veth0");
+ response_proto.set_host_ifname("arc_ns0");
+ response_proto.set_peer_ipv4_address(Ipv4Addr(100, 115, 92, 130));
+ response_proto.set_host_ipv4_address(Ipv4Addr(100, 115, 92, 129));
+ auto* response_subnet = response_proto.mutable_ipv4_subnet();
+ response_subnet->set_prefix_len(30);
+ response_subnet->set_base_addr(Ipv4Addr(100, 115, 92, 128));
+ std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter response_writer(response.get());
+ response_writer.AppendProtoAsArrayOfBytes(response_proto);
+ EXPECT_CALL(*proxy, CallMethodAndBlock(_, _))
+ .WillOnce(Return(ByMove(std::move(response))));
+
+ result = client.ConnectNamespace(pid, outboud_ifname, false);
+ EXPECT_TRUE(result.first.is_valid());
+ EXPECT_EQ("arc_ns0", result.second.host_ifname());
+ EXPECT_EQ("veth0", result.second.peer_ifname());
+ EXPECT_EQ(30, result.second.ipv4_subnet().prefix_len());
+ EXPECT_EQ(Ipv4Addr(100, 115, 92, 128),
+ result.second.ipv4_subnet().base_addr());
+ EXPECT_EQ(Ipv4Addr(100, 115, 92, 129), result.second.host_ipv4_address());
+ EXPECT_EQ(Ipv4Addr(100, 115, 92, 130), result.second.peer_ipv4_address());
+}
+
+} // namespace patchpanel
diff --git a/patchpanel/dbus/libpatchpanel-client.pc.in b/patchpanel/dbus/libpatchpanel-client.pc.in
new file mode 100644
index 0000000..6912657
--- /dev/null
+++ b/patchpanel/dbus/libpatchpanel-client.pc.in
@@ -0,0 +1,9 @@
+bslot=@BSLOT@
+include_dir=@INCLUDE_DIR@
+
+Name: libpatchpanel-client
+Description: PatchPanel networking client library
+Version: ${bslot}
+CFlags: -I${include_dir}
+Libs: -lpatchpanel-client
+
diff --git a/patchpanel/dbus/preinstall.sh b/patchpanel/dbus/preinstall.sh
new file mode 100755
index 0000000..7d2484f
--- /dev/null
+++ b/patchpanel/dbus/preinstall.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# Copyright 2020 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.
+
+set -e
+
+v=$1
+include_dir=$2
+out=$3
+
+sed \
+ -e "s/@BSLOT@/${v}/g" \
+ -e "s:@INCLUDE_DIR@:${include_dir}:g" \
+ "libpatchpanel-client.pc.in" > "${out}/libpatchpanel-client.pc"