| // Copyright 2016 The ChromiumOS Authors |
| // 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 <string.h> |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/memory/weak_ptr.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/dbus/dbus_proxy_util.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dbus/message.h> |
| #include <dbus/object_path.h> |
| #include <patchpanel/proto_bindings/patchpanel_service.pb.h> |
| |
| #include "patchpanel/net_util.h" |
| |
| namespace patchpanel { |
| |
| namespace { |
| |
| void CopyBytes(const std::string& from, std::vector<uint8_t>* to) { |
| to->assign(from.begin(), from.end()); |
| } |
| |
| patchpanel::TrafficCounter::Source ConvertTrafficSource( |
| Client::TrafficSource source) { |
| switch (source) { |
| case Client::TrafficSource::kUnknown: |
| return patchpanel::TrafficCounter::UNKNOWN; |
| case Client::TrafficSource::kChrome: |
| return patchpanel::TrafficCounter::CHROME; |
| case Client::TrafficSource::kUser: |
| return patchpanel::TrafficCounter::USER; |
| case Client::TrafficSource::kArc: |
| return patchpanel::TrafficCounter::ARC; |
| case Client::TrafficSource::kCrosVm: |
| return patchpanel::TrafficCounter::CROSVM; |
| case Client::TrafficSource::kPluginVm: |
| return patchpanel::TrafficCounter::PLUGINVM; |
| case Client::TrafficSource::kUpdateEngine: |
| return patchpanel::TrafficCounter::UPDATE_ENGINE; |
| case Client::TrafficSource::kVpn: |
| return patchpanel::TrafficCounter::VPN; |
| case Client::TrafficSource::kSystem: |
| return patchpanel::TrafficCounter::SYSTEM; |
| } |
| } |
| |
| Client::TrafficSource ConvertTrafficSource( |
| patchpanel::TrafficCounter::Source source) { |
| switch (source) { |
| case patchpanel::TrafficCounter::CHROME: |
| return Client::TrafficSource::kChrome; |
| case patchpanel::TrafficCounter::USER: |
| return Client::TrafficSource::kUser; |
| case patchpanel::TrafficCounter::ARC: |
| return Client::TrafficSource::kArc; |
| case patchpanel::TrafficCounter::CROSVM: |
| return Client::TrafficSource::kCrosVm; |
| case patchpanel::TrafficCounter::PLUGINVM: |
| return Client::TrafficSource::kPluginVm; |
| case patchpanel::TrafficCounter::UPDATE_ENGINE: |
| return Client::TrafficSource::kUpdateEngine; |
| case patchpanel::TrafficCounter::VPN: |
| return Client::TrafficSource::kVpn; |
| case patchpanel::TrafficCounter::SYSTEM: |
| return Client::TrafficSource::kSystem; |
| default: |
| return Client::TrafficSource::kUnknown; |
| } |
| } |
| |
| patchpanel::NeighborReachabilityEventSignal::Role ConvertNeighborRole( |
| Client::NeighborRole role) { |
| switch (role) { |
| case Client::NeighborRole::kGateway: |
| return patchpanel::NeighborReachabilityEventSignal::GATEWAY; |
| case Client::NeighborRole::kDnsServer: |
| return patchpanel::NeighborReachabilityEventSignal::DNS_SERVER; |
| case Client::NeighborRole::kGatewayAndDnsServer: |
| return patchpanel::NeighborReachabilityEventSignal:: |
| GATEWAY_AND_DNS_SERVER; |
| } |
| } |
| |
| patchpanel::NeighborReachabilityEventSignal::EventType ConvertNeighborStatus( |
| Client::NeighborStatus status) { |
| switch (status) { |
| case Client::NeighborStatus::kFailed: |
| return patchpanel::NeighborReachabilityEventSignal::FAILED; |
| case Client::NeighborStatus::kReachable: |
| return patchpanel::NeighborReachabilityEventSignal::REACHABLE; |
| } |
| } |
| |
| patchpanel::ModifyPortRuleRequest::Operation ConvertFirewallRequestOperation( |
| Client::FirewallRequestOperation op) { |
| switch (op) { |
| case Client::FirewallRequestOperation::kCreate: |
| return ModifyPortRuleRequest::CREATE; |
| case Client::FirewallRequestOperation::kDelete: |
| return ModifyPortRuleRequest::DELETE; |
| } |
| } |
| |
| patchpanel::ModifyPortRuleRequest::RuleType ConvertFirewallRequestType( |
| Client::FirewallRequestType type) { |
| switch (type) { |
| case Client::FirewallRequestType::kAccess: |
| return ModifyPortRuleRequest::ACCESS; |
| case Client::FirewallRequestType::kLockdown: |
| return ModifyPortRuleRequest::LOCKDOWN; |
| case Client::FirewallRequestType::kForwarding: |
| return ModifyPortRuleRequest::FORWARDING; |
| } |
| } |
| |
| patchpanel::ModifyPortRuleRequest::Protocol ConvertFirewallRequestProtocol( |
| Client::FirewallRequestProtocol protocol) { |
| switch (protocol) { |
| case Client::FirewallRequestProtocol::kTcp: |
| return ModifyPortRuleRequest::TCP; |
| case Client::FirewallRequestProtocol::kUdp: |
| return ModifyPortRuleRequest::UDP; |
| } |
| } |
| |
| patchpanel::SetDnsRedirectionRuleRequest::RuleType |
| ConvertDnsRedirectionRequestType(Client::DnsRedirectionRequestType type) { |
| switch (type) { |
| case Client::DnsRedirectionRequestType::kDefault: |
| return patchpanel::SetDnsRedirectionRuleRequest::DEFAULT; |
| case Client::DnsRedirectionRequestType::kArc: |
| return patchpanel::SetDnsRedirectionRuleRequest::ARC; |
| case Client::DnsRedirectionRequestType::kUser: |
| return patchpanel::SetDnsRedirectionRuleRequest::USER; |
| case Client::DnsRedirectionRequestType::kExcludeDestination: |
| return patchpanel::SetDnsRedirectionRuleRequest::EXCLUDE_DESTINATION; |
| } |
| } |
| |
| std::vector<uint8_t> ConvertIPv4Addr(uint32_t in) { |
| std::vector<uint8_t> out; |
| out.resize(4); |
| memcpy(out.data(), &in, sizeof(in)); |
| return out; |
| } |
| |
| Client::IPv4Subnet ConvertIPv4Subnet(const IPv4Subnet& in) { |
| Client::IPv4Subnet out = {}; |
| out.base_addr.assign(in.addr().begin(), in.addr().begin()); |
| CopyBytes(in.addr(), &out.base_addr); |
| out.prefix_len = static_cast<int>(in.prefix_len()); |
| return out; |
| } |
| |
| std::optional<Client::TrafficCounter> ConvertTrafficCounter( |
| const TrafficCounter& in) { |
| auto out = std::make_optional<Client::TrafficCounter>(); |
| out->rx_bytes = in.rx_bytes(); |
| out->tx_bytes = in.tx_bytes(); |
| out->rx_packets = in.rx_packets(); |
| out->tx_packets = in.tx_packets(); |
| out->ifname = in.device(); |
| out->source = ConvertTrafficSource(in.source()); |
| switch (in.ip_family()) { |
| case patchpanel::TrafficCounter::IPV4: |
| out->ip_family = Client::IPFamily::kIPv4; |
| break; |
| case patchpanel::TrafficCounter::IPV6: |
| out->ip_family = Client::IPFamily::kIPv6; |
| break; |
| default: |
| LOG(ERROR) << __func__ << ": Unknown IpFamily " |
| << patchpanel::TrafficCounter::IpFamily_Name(in.ip_family()); |
| return std::nullopt; |
| } |
| return out; |
| } |
| |
| std::optional<Client::VirtualDevice> ConvertVirtualDevice( |
| const NetworkDevice& in) { |
| auto out = std::make_optional<Client::VirtualDevice>(); |
| out->ifname = in.ifname(); |
| out->phys_ifname = in.phys_ifname(); |
| out->guest_ifname = in.guest_ifname(); |
| out->ipv4_addr = ConvertIPv4Addr(in.ipv4_addr()); |
| out->host_ipv4_addr = ConvertIPv4Addr(in.host_ipv4_addr()); |
| out->ipv4_subnet = ConvertIPv4Subnet(in.ipv4_subnet()); |
| CopyBytes(in.dns_proxy_ipv4_addr(), &out->dns_proxy_ipv4_addr); |
| CopyBytes(in.dns_proxy_ipv6_addr(), &out->dns_proxy_ipv6_addr); |
| switch (in.guest_type()) { |
| case patchpanel::NetworkDevice::ARC: |
| out->guest_type = Client::GuestType::kArcContainer; |
| break; |
| case patchpanel::NetworkDevice::ARCVM: |
| out->guest_type = Client::GuestType::kArcVm; |
| break; |
| case patchpanel::NetworkDevice::TERMINA_VM: |
| out->guest_type = Client::GuestType::kTerminaVm; |
| break; |
| case patchpanel::NetworkDevice::PLUGIN_VM: |
| out->guest_type = Client::GuestType::kPluginVm; |
| break; |
| default: |
| LOG(ERROR) << __func__ << ": Unknown GuestType " |
| << patchpanel::NetworkDevice::GuestType_Name(in.guest_type()); |
| return std::nullopt; |
| } |
| return out; |
| } |
| |
| Client::NetworkClientInfo ConvertNetworkClientInfo( |
| const NetworkClientInfo& in) { |
| Client::NetworkClientInfo out; |
| std::copy(in.mac_addr().begin(), in.mac_addr().end(), |
| std::back_inserter(out.mac_addr)); |
| std::copy(in.ipv4_addr().begin(), in.ipv4_addr().end(), |
| std::back_inserter(out.ipv4_addr)); |
| for (const auto& ipv6_addr : in.ipv6_addresses()) { |
| out.ipv6_addresses.emplace_back(ipv6_addr.begin(), ipv6_addr.end()); |
| } |
| out.hostname = in.hostname(); |
| out.vendor_class = in.vendor_class(); |
| return out; |
| } |
| |
| Client::DownstreamNetwork ConvertDownstreamNetwork( |
| const DownstreamNetwork& in) { |
| Client::DownstreamNetwork out; |
| out.ifname = in.downstream_ifname(); |
| out.ipv4_subnet = ConvertIPv4Subnet(in.ipv4_subnet()); |
| CopyBytes(in.ipv4_gateway_addr(), &out.ipv4_gateway_addr); |
| return out; |
| } |
| |
| std::optional<Client::NeighborReachabilityEvent> |
| ConvertNeighborReachabilityEvent(const NeighborReachabilityEventSignal& in) { |
| auto out = std::make_optional<Client::NeighborReachabilityEvent>(); |
| out->ifindex = in.ifindex(); |
| out->ip_addr = in.ip_addr(); |
| switch (in.role()) { |
| case patchpanel::NeighborReachabilityEventSignal::GATEWAY: |
| out->role = Client::NeighborRole::kGateway; |
| break; |
| case patchpanel::NeighborReachabilityEventSignal::DNS_SERVER: |
| out->role = Client::NeighborRole::kDnsServer; |
| break; |
| case patchpanel::NeighborReachabilityEventSignal::GATEWAY_AND_DNS_SERVER: |
| out->role = Client::NeighborRole::kGatewayAndDnsServer; |
| break; |
| default: |
| LOG(ERROR) << __func__ << ": Unknown NeighborReachability role " |
| << patchpanel::NeighborReachabilityEventSignal::Role_Name( |
| in.role()); |
| return std::nullopt; |
| } |
| switch (in.type()) { |
| case patchpanel::NeighborReachabilityEventSignal::FAILED: |
| out->status = Client::NeighborStatus::kFailed; |
| break; |
| case patchpanel::NeighborReachabilityEventSignal::REACHABLE: |
| out->status = Client::NeighborStatus::kReachable; |
| break; |
| default: |
| LOG(ERROR) << __func__ << ": Unknown NeighborReachability event type " |
| << patchpanel::NeighborReachabilityEventSignal::EventType_Name( |
| in.type()); |
| return std::nullopt; |
| } |
| return out; |
| } |
| |
| std::optional<Client::VirtualDeviceEvent> ConvertVirtualDeviceEvent( |
| const NetworkDeviceChangedSignal& in) { |
| switch (in.event()) { |
| case patchpanel::NetworkDeviceChangedSignal::DEVICE_ADDED: |
| return Client::VirtualDeviceEvent::kAdded; |
| case patchpanel::NetworkDeviceChangedSignal::DEVICE_REMOVED: |
| return Client::VirtualDeviceEvent::kRemoved; |
| default: |
| LOG(ERROR) << __func__ << ": Unknown NetworkDeviceChangedSignal event " |
| << patchpanel::NetworkDeviceChangedSignal::Event_Name( |
| in.event()); |
| return std::nullopt; |
| } |
| } |
| |
| Client::ConnectedNamespace ConvertConnectedNamespace( |
| const ConnectNamespaceResponse& in) { |
| Client::ConnectedNamespace out; |
| out.ipv4_subnet = ConvertIPv4Subnet(in.ipv4_subnet()); |
| out.peer_ifname = in.peer_ifname(); |
| out.peer_ipv4_address = ConvertIPv4Addr(in.peer_ipv4_address()); |
| out.host_ifname = in.host_ifname(); |
| out.host_ipv4_address = ConvertIPv4Addr(in.host_ipv4_address()); |
| out.netns_name = in.netns_name(); |
| return out; |
| } |
| |
| 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; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const SetDnsRedirectionRuleRequest& request) { |
| stream << "{ proxy type: " |
| << SetDnsRedirectionRuleRequest::RuleType_Name(request.type()); |
| if (!request.input_ifname().empty()) { |
| stream << ", input interface name: " << request.input_ifname(); |
| } |
| if (!request.proxy_address().empty()) { |
| stream << ", proxy IPv4 address: " << request.proxy_address(); |
| } |
| if (!request.nameservers().empty()) { |
| std::vector<std::string> nameservers; |
| for (const auto& ns : request.nameservers()) { |
| nameservers.emplace_back(ns); |
| } |
| stream << ", nameserver(s): " << base::JoinString(nameservers, ","); |
| } |
| stream << " }"; |
| return stream; |
| } |
| |
| // Prepares a pair of ScopedFDs corresponding to the write end (pair first |
| // element) and read end (pair second lemenet) of a Linux pipe and appends the |
| // read end to the given |writer| to send to patchpanel. The client must keep |
| // the read end alive until the DBus request is successfully sent. The client |
| // must keep the write end alive until the setup requested from patchpanel is |
| // not necessary anymore. |
| std::pair<base::ScopedFD, base::ScopedFD> CommitLifelineFd( |
| dbus::MessageWriter* writer) { |
| 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 {}; |
| } |
| // MessageWriter::AppendFileDescriptor duplicates the fd, so the original read |
| // fd is given back to the caller using ScopedFD to make sure the it is |
| // eventually closed. |
| writer->AppendFileDescriptor(pipe_fds[1]); |
| return {base::ScopedFD(pipe_fds[0]), base::ScopedFD(pipe_fds[1])}; |
| } |
| |
| void OnGetTrafficCountersDBusResponse( |
| Client::GetTrafficCountersCallback callback, |
| dbus::Response* dbus_response) { |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to send TrafficCountersRequest message to patchpanel " |
| "service"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| TrafficCountersResponse response; |
| dbus::MessageReader reader(dbus_response); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse TrafficCountersResponse proto"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| std::vector<Client::TrafficCounter> counters; |
| for (const auto& proto_counter : response.counters()) { |
| auto client_counter = ConvertTrafficCounter(proto_counter); |
| if (client_counter) { |
| counters.push_back(*client_counter); |
| } |
| } |
| std::move(callback).Run(counters); |
| } |
| |
| void OnNetworkDeviceChangedSignal( |
| const Client::VirtualDeviceEventHandler& handler, dbus::Signal* signal) { |
| dbus::MessageReader reader(signal); |
| NetworkDeviceChangedSignal proto; |
| if (!reader.PopArrayOfBytesAsProto(&proto)) { |
| LOG(ERROR) << "Failed to parse NetworkDeviceChangedSignal proto"; |
| return; |
| } |
| |
| const auto event = ConvertVirtualDeviceEvent(proto); |
| if (!event) { |
| return; |
| } |
| const auto device = ConvertVirtualDevice(proto.device()); |
| if (device) { |
| handler.Run(*event, *device); |
| } |
| } |
| |
| void OnNeighborReachabilityEventSignal( |
| const Client::NeighborReachabilityEventHandler& handler, |
| dbus::Signal* signal) { |
| dbus::MessageReader reader(signal); |
| NeighborReachabilityEventSignal proto; |
| if (!reader.PopArrayOfBytesAsProto(&proto)) { |
| LOG(ERROR) << "Failed to parse NeighborConnectedStateChangedSignal proto"; |
| return; |
| } |
| |
| const auto event = ConvertNeighborReachabilityEvent(proto); |
| if (event) { |
| handler.Run(*event); |
| } |
| } |
| |
| void OnSignalConnectedCallback(const std::string& interface_name, |
| const std::string& signal_name, |
| bool success) { |
| if (!success) |
| LOG(ERROR) << "Failed to connect to " << signal_name; |
| } |
| |
| // Helper static function to process answers to CreateTetheredNetwork calls. |
| void OnTetheredNetworkResponse(Client::CreateTetheredNetworkCallback callback, |
| base::ScopedFD fd_local, |
| dbus::Response* dbus_response) { |
| if (!dbus_response) { |
| LOG(ERROR) |
| << kCreateTetheredNetworkMethod |
| << ": Failed to send TetheredNetworkRequest message to patchpanel"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| TetheredNetworkResponse response; |
| dbus::MessageReader reader(dbus_response); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << kCreateTetheredNetworkMethod |
| << ": Failed to parse TetheredNetworkResponse proto"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| if (response.response_code() != DownstreamNetworkResult::SUCCESS) { |
| LOG(ERROR) << kCreateTetheredNetworkMethod << " failed: " |
| << patchpanel::DownstreamNetworkResult_Name( |
| response.response_code()); |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| std::move(callback).Run(std::move(fd_local)); |
| } |
| |
| // Helper static function to process answers to CreateLocalOnlyNetwork calls. |
| void OnLocalOnlyNetworkResponse(Client::CreateLocalOnlyNetworkCallback callback, |
| base::ScopedFD fd_local, |
| dbus::Response* dbus_response) { |
| if (!dbus_response) { |
| LOG(ERROR) |
| << kCreateLocalOnlyNetworkMethod |
| << ": Failed to send LocalOnlyNetworkRequest message to patchpanel"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| LocalOnlyNetworkResponse response; |
| dbus::MessageReader reader(dbus_response); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << kCreateLocalOnlyNetworkMethod |
| << ": Failed to parse LocalOnlyNetworkResponse proto"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| if (response.response_code() != DownstreamNetworkResult::SUCCESS) { |
| LOG(ERROR) << kCreateLocalOnlyNetworkMethod << " failed: " |
| << patchpanel::DownstreamNetworkResult_Name( |
| response.response_code()); |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| std::move(callback).Run(std::move(fd_local)); |
| } |
| |
| // Helper static function to process answers to DownstreamNetworkInfo calls. |
| void OnDownstreamNetworkInfoResponse( |
| Client::DownstreamNetworkInfoCallback callback, |
| dbus::Response* dbus_response) { |
| if (!dbus_response) { |
| LOG(ERROR) << kDownstreamNetworkInfoMethod |
| << ": Failed to send DownstreamNetworkInfoRequest message to " |
| "patchpanel"; |
| std::move(callback).Run(false, {}, {}); |
| return; |
| } |
| |
| DownstreamNetworkInfoResponse response; |
| dbus::MessageReader reader(dbus_response); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << kDownstreamNetworkInfoMethod |
| << ": Failed to parse DownstreamNetworkInfoResponse proto"; |
| std::move(callback).Run(false, {}, {}); |
| return; |
| } |
| |
| auto downstream_network = |
| ConvertDownstreamNetwork(response.downstream_network()); |
| |
| std::vector<Client::NetworkClientInfo> clients_info; |
| for (const auto& ci : response.clients_info()) { |
| clients_info.push_back(ConvertNetworkClientInfo(ci)); |
| } |
| |
| std::move(callback).Run(true, downstream_network, clients_info); |
| } |
| |
| class ClientImpl : public Client { |
| public: |
| ClientImpl(const scoped_refptr<dbus::Bus>& bus, |
| dbus::ObjectProxy* proxy, |
| bool owns_bus) |
| : bus_(std::move(bus)), proxy_(proxy), owns_bus_(owns_bus) {} |
| ClientImpl(const ClientImpl&) = delete; |
| ClientImpl& operator=(const ClientImpl&) = delete; |
| |
| ~ClientImpl(); |
| |
| void RegisterOnAvailableCallback( |
| base::RepeatingCallback<void(bool)> callback) override; |
| |
| void RegisterProcessChangedCallback( |
| base::RepeatingCallback<void(bool)> callback) override; |
| |
| bool NotifyArcStartup(pid_t pid) override; |
| bool NotifyArcShutdown() override; |
| |
| std::vector<Client::VirtualDevice> NotifyArcVmStartup(uint32_t cid) override; |
| bool NotifyArcVmShutdown(uint32_t cid) override; |
| |
| bool NotifyTerminaVmStartup(uint32_t cid, |
| Client::VirtualDevice* device, |
| Client::IPv4Subnet* container_subnet) override; |
| bool NotifyTerminaVmShutdown(uint32_t cid) override; |
| |
| bool NotifyPluginVmStartup(uint64_t vm_id, |
| int subnet_index, |
| Client::VirtualDevice* device) override; |
| bool NotifyPluginVmShutdown(uint64_t vm_id) override; |
| |
| bool DefaultVpnRouting(int socket) override; |
| |
| bool RouteOnVpn(int socket) override; |
| |
| bool BypassVpn(int socket) override; |
| |
| std::pair<base::ScopedFD, Client::ConnectedNamespace> ConnectNamespace( |
| pid_t pid, |
| const std::string& outbound_ifname, |
| bool forward_user_traffic, |
| bool route_on_vpn, |
| Client::TrafficSource traffic_source) override; |
| |
| void GetTrafficCounters(const std::set<std::string>& devices, |
| GetTrafficCountersCallback callback) override; |
| |
| bool ModifyPortRule(Client::FirewallRequestOperation op, |
| Client::FirewallRequestType type, |
| Client::FirewallRequestProtocol 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) override; |
| |
| bool SetVpnLockdown(bool enable) override; |
| |
| base::ScopedFD RedirectDns(Client::DnsRedirectionRequestType type, |
| const std::string& input_ifname, |
| const std::string& proxy_address, |
| const std::vector<std::string>& nameservers, |
| const std::string& host_ifname) override; |
| |
| std::vector<Client::VirtualDevice> GetDevices() override; |
| |
| void RegisterVirtualDeviceEventHandler( |
| VirtualDeviceEventHandler handler) override; |
| |
| void RegisterNeighborReachabilityEventHandler( |
| NeighborReachabilityEventHandler handler) override; |
| |
| bool CreateTetheredNetwork(const std::string& downstream_ifname, |
| const std::string& upstream_ifname, |
| const std::optional<DHCPOptions>& dhcp_options, |
| const std::optional<int>& mtu, |
| CreateTetheredNetworkCallback callback) override; |
| |
| bool CreateLocalOnlyNetwork(const std::string& ifname, |
| CreateLocalOnlyNetworkCallback callback) override; |
| |
| bool GetDownstreamNetworkInfo( |
| const std::string& ifname, |
| DownstreamNetworkInfoCallback callback) override; |
| |
| private: |
| scoped_refptr<dbus::Bus> bus_; |
| dbus::ObjectProxy* proxy_ = nullptr; // owned by |bus_| |
| bool owns_bus_; // Yes if |bus_| is created by Client::New |
| |
| base::RepeatingCallback<void(bool)> owner_callback_; |
| |
| void OnOwnerChanged(const std::string& old_owner, |
| const std::string& new_owner); |
| |
| bool SendSetVpnIntentRequest(int socket, |
| SetVpnIntentRequest::VpnRoutingPolicy policy); |
| |
| base::WeakPtrFactory<ClientImpl> weak_factory_{this}; |
| }; |
| |
| ClientImpl::~ClientImpl() { |
| if (bus_ && owns_bus_) |
| bus_->ShutdownAndBlock(); |
| } |
| |
| void ClientImpl::RegisterOnAvailableCallback( |
| base::RepeatingCallback<void(bool)> callback) { |
| if (!proxy_) { |
| LOG(ERROR) << "Cannot register callback - no proxy"; |
| return; |
| } |
| proxy_->WaitForServiceToBeAvailable(callback); |
| } |
| |
| void ClientImpl::RegisterProcessChangedCallback( |
| base::RepeatingCallback<void(bool)> callback) { |
| owner_callback_ = callback; |
| bus_->GetObjectProxy(kPatchPanelServiceName, dbus::ObjectPath{"/"}) |
| ->SetNameOwnerChangedCallback(base::BindRepeating( |
| &ClientImpl::OnOwnerChanged, weak_factory_.GetWeakPtr())); |
| } |
| |
| void ClientImpl::OnOwnerChanged(const std::string& old_owner, |
| const std::string& new_owner) { |
| if (new_owner.empty()) { |
| LOG(INFO) << "Patchpanel lost"; |
| if (!owner_callback_.is_null()) |
| owner_callback_.Run(false); |
| return; |
| } |
| |
| LOG(INFO) << "Patchpanel reset"; |
| if (!owner_callback_.is_null()) |
| owner_callback_.Run(true); |
| } |
| |
| bool ClientImpl::NotifyArcStartup(pid_t pid) { |
| dbus::MethodCall method_call(kPatchPanelInterface, kArcStartupMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| ArcStartupRequest request; |
| request.set_pid(pid); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode ArcStartupRequest proto"; |
| return false; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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<Client::VirtualDevice> ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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<Client::VirtualDevice> devices; |
| for (const auto& d : response.devices()) { |
| const auto device = ConvertVirtualDevice(d); |
| if (device) { |
| devices.push_back(*device); |
| } |
| } |
| return devices; |
| } |
| |
| bool ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 ClientImpl::NotifyTerminaVmStartup(uint32_t cid, |
| Client::VirtualDevice* device, |
| Client::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 virtual device found"; |
| return false; |
| } |
| |
| const auto response_device = ConvertVirtualDevice(response.device()); |
| if (!response_device) { |
| LOG(ERROR) << "Invalid virtual device response"; |
| return false; |
| } |
| *device = *response_device; |
| |
| if (response.has_container_subnet()) { |
| *container_subnet = ConvertIPv4Subnet(response.container_subnet()); |
| } else { |
| LOG(WARNING) << "No container subnet found"; |
| } |
| |
| return true; |
| } |
| |
| bool ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 ClientImpl::NotifyPluginVmStartup(uint64_t vm_id, |
| int subnet_index, |
| Client::VirtualDevice* 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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 virtual device found"; |
| return false; |
| } |
| |
| const auto response_device = ConvertVirtualDevice(response.device()); |
| if (!response_device) { |
| LOG(ERROR) << "Invalid virtual device response"; |
| return false; |
| } |
| |
| *device = *response_device; |
| return true; |
| } |
| |
| bool ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 ClientImpl::DefaultVpnRouting(int socket) { |
| return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::DEFAULT_ROUTING); |
| } |
| |
| bool ClientImpl::RouteOnVpn(int socket) { |
| return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::ROUTE_ON_VPN); |
| } |
| |
| bool ClientImpl::BypassVpn(int socket) { |
| return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::BYPASS_VPN); |
| } |
| |
| bool ClientImpl::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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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, Client::ConnectedNamespace> |
| ClientImpl::ConnectNamespace(pid_t pid, |
| const std::string& outbound_ifname, |
| bool forward_user_traffic, |
| bool route_on_vpn, |
| Client::TrafficSource traffic_source) { |
| // 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); |
| request.set_route_on_vpn(route_on_vpn); |
| request.set_traffic_source(ConvertTrafficSource(traffic_source)); |
| |
| 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. |
| auto [fd_local, fd_remote] = CommitLifelineFd(&writer); |
| if (!fd_local.is_valid()) { |
| LOG(ERROR) |
| << "Cannot send ConnectNamespace message to patchpanel: no lifeline fd"; |
| return {}; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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 {}; |
| } |
| |
| auto connected_ns = ConvertConnectedNamespace(response); |
| |
| std::string subnet_info = IPv4AddressToCidrString( |
| connected_ns.ipv4_subnet.base_addr, connected_ns.ipv4_subnet.prefix_len); |
| LOG(INFO) << "ConnectNamespace for netns pid " << pid |
| << " succeeded: peer_ifname=" << connected_ns.peer_ifname |
| << " peer_ipv4_address=" |
| << IPv4AddressToString(connected_ns.peer_ipv4_address) |
| << " host_ifname=" << connected_ns.host_ifname |
| << " host_ipv4_address=" |
| << IPv4AddressToString(connected_ns.host_ipv4_address) |
| << " subnet=" << subnet_info; |
| |
| return std::make_pair(std::move(fd_local), std::move(connected_ns)); |
| } |
| |
| void ClientImpl::GetTrafficCounters(const std::set<std::string>& devices, |
| GetTrafficCountersCallback callback) { |
| 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"; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| proxy_->CallMethod( |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::BindOnce(&OnGetTrafficCountersDBusResponse, std::move(callback))); |
| } |
| |
| bool ClientImpl::ModifyPortRule(Client::FirewallRequestOperation op, |
| Client::FirewallRequestType type, |
| Client::FirewallRequestProtocol 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(ConvertFirewallRequestOperation(op)); |
| request.set_type(ConvertFirewallRequestType(type)); |
| request.set_proto(ConvertFirewallRequestProtocol(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 = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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; |
| } |
| |
| bool ClientImpl::SetVpnLockdown(bool enable) { |
| dbus::MethodCall method_call(kPatchPanelInterface, kSetVpnLockdown); |
| dbus::MessageWriter writer(&method_call); |
| |
| SetVpnLockdownRequest request; |
| SetVpnLockdownResponse response; |
| |
| request.set_enable_vpn_lockdown(enable); |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode SetVpnLockdownRequest proto"; |
| return false; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to call SetVpnLockdown patchpanel API"; |
| return false; |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse SetVpnLockdownResponse"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::ScopedFD ClientImpl::RedirectDns( |
| Client::DnsRedirectionRequestType type, |
| const std::string& input_ifname, |
| const std::string& proxy_address, |
| const std::vector<std::string>& nameservers, |
| const std::string& host_ifname) { |
| dbus::MethodCall method_call(kPatchPanelInterface, |
| kSetDnsRedirectionRuleMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| SetDnsRedirectionRuleRequest request; |
| SetDnsRedirectionRuleResponse response; |
| |
| request.set_type(ConvertDnsRedirectionRequestType(type)); |
| request.set_input_ifname(input_ifname); |
| request.set_proxy_address(proxy_address); |
| request.set_host_ifname(host_ifname); |
| for (const auto& nameserver : nameservers) { |
| request.add_nameservers(nameserver); |
| } |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode SetDnsRedirectionRuleRequest proto " |
| << request; |
| return {}; |
| } |
| |
| // Prepare an fd pair and append one fd directly after the serialized request. |
| auto [fd_local, fd_remote] = CommitLifelineFd(&writer); |
| if (!fd_local.is_valid()) { |
| LOG(ERROR) << "Cannot send SetDnsRedirectionRuleRequest message to " |
| "patchpanel: no lifeline fd"; |
| return {}; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to send SetDnsRedirectionRuleRequest message to " |
| "patchpanel service " |
| << request; |
| return {}; |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse SetDnsRedirectionRuleResponse proto " |
| << request; |
| return {}; |
| } |
| |
| if (!response.success()) { |
| LOG(ERROR) << "SetDnsRedirectionRuleRequest failed " << request; |
| return {}; |
| } |
| return std::move(fd_local); |
| } |
| |
| std::vector<Client::VirtualDevice> ClientImpl::GetDevices() { |
| dbus::MethodCall method_call(kPatchPanelInterface, kGetDevicesMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| GetDevicesRequest request; |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode GetDevicesRequest proto"; |
| return {}; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus_, proxy_, &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()); |
| GetDevicesResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse response proto"; |
| return {}; |
| } |
| |
| std::vector<Client::VirtualDevice> devices; |
| for (const auto& d : response.devices()) { |
| const auto device = ConvertVirtualDevice(d); |
| if (device) { |
| devices.push_back(*device); |
| } |
| } |
| return devices; |
| } |
| |
| void ClientImpl::RegisterVirtualDeviceEventHandler( |
| VirtualDeviceEventHandler handler) { |
| proxy_->ConnectToSignal( |
| kPatchPanelInterface, kNetworkDeviceChangedSignal, |
| base::BindRepeating(OnNetworkDeviceChangedSignal, handler), |
| base::BindOnce(OnSignalConnectedCallback)); |
| } |
| |
| void ClientImpl::RegisterNeighborReachabilityEventHandler( |
| NeighborReachabilityEventHandler handler) { |
| proxy_->ConnectToSignal( |
| kPatchPanelInterface, kNeighborReachabilityEventSignal, |
| base::BindRepeating(OnNeighborReachabilityEventSignal, handler), |
| base::BindOnce(OnSignalConnectedCallback)); |
| } |
| |
| bool ClientImpl::CreateTetheredNetwork( |
| const std::string& downstream_ifname, |
| const std::string& upstream_ifname, |
| const std::optional<DHCPOptions>& dhcp_options, |
| const std::optional<int>& mtu, |
| CreateTetheredNetworkCallback callback) { |
| dbus::MethodCall method_call(kPatchPanelInterface, |
| kCreateTetheredNetworkMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| TetheredNetworkRequest request; |
| request.set_ifname(downstream_ifname); |
| request.set_upstream_ifname(upstream_ifname); |
| if (mtu) { |
| request.set_mtu(*mtu); |
| } |
| // TODO(b/239559602) Fill out DHCP options: |
| // - If the upstream network has a DHCP lease, copy relevant options. |
| // - Option 43 with ANDROID_METERED if the upstream network is metered. |
| // - Forward DHCP WPAD proxy configuration if advertised by the upstream |
| // network. |
| auto* ipv4_config = request.mutable_ipv4_config(); |
| if (dhcp_options) { |
| ipv4_config->set_use_dhcp(true); |
| for (const auto& dns_server : dhcp_options->dns_server_addresses) { |
| ipv4_config->add_dns_servers(dns_server.data(), dns_server.size()); |
| } |
| for (const auto& domain_search : dhcp_options->domain_search_list) { |
| ipv4_config->add_domain_searches(domain_search); |
| } |
| } |
| |
| request.set_enable_ipv6(true); |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << kCreateTetheredNetworkMethod << "(" << downstream_ifname |
| << "," << upstream_ifname |
| << "): Failed to encode TetheredNetworkRequest proto"; |
| return false; |
| } |
| |
| // Prepare an fd pair and append one fd directly after the serialized request. |
| auto [fd_local, fd_remote] = CommitLifelineFd(&writer); |
| if (!fd_local.is_valid()) { |
| LOG(ERROR) << kCreateTetheredNetworkMethod << "(" << downstream_ifname |
| << "," << upstream_ifname << "): Cannot create lifeline fds"; |
| return false; |
| } |
| |
| proxy_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::BindOnce(&OnTetheredNetworkResponse, |
| std::move(callback), std::move(fd_local))); |
| return true; |
| } |
| |
| bool ClientImpl::CreateLocalOnlyNetwork( |
| const std::string& ifname, CreateLocalOnlyNetworkCallback callback) { |
| dbus::MethodCall method_call(kPatchPanelInterface, |
| kCreateLocalOnlyNetworkMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| LocalOnlyNetworkRequest request; |
| request.set_ifname(ifname); |
| auto* ipv4_config = request.mutable_ipv4_config(); |
| ipv4_config->set_use_dhcp(true); |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << kCreateLocalOnlyNetworkMethod |
| << ": Failed to encode LocalOnlyNetworkRequest proto"; |
| return false; |
| } |
| |
| // Prepare an fd pair and append one fd directly after the serialized request. |
| auto [fd_local, fd_remote] = CommitLifelineFd(&writer); |
| if (!fd_local.is_valid()) { |
| LOG(ERROR) << kCreateLocalOnlyNetworkMethod |
| << ": Cannot create lifeline fds"; |
| return false; |
| } |
| |
| proxy_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::BindOnce(&OnLocalOnlyNetworkResponse, |
| std::move(callback), std::move(fd_local))); |
| return true; |
| } |
| |
| bool ClientImpl::GetDownstreamNetworkInfo( |
| const std::string& ifname, DownstreamNetworkInfoCallback callback) { |
| dbus::MethodCall method_call(kPatchPanelInterface, |
| kDownstreamNetworkInfoMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| DownstreamNetworkInfoRequest request; |
| request.set_downstream_ifname(ifname); |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode DownstreamNetworkInfoRequest proto"; |
| return false; |
| } |
| |
| proxy_->CallMethod( |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::BindOnce(&OnDownstreamNetworkInfoResponse, std::move(callback))); |
| return true; |
| } |
| |
| dbus::ObjectProxy* GetProxy(const scoped_refptr<dbus::Bus>& bus) { |
| dbus::ObjectProxy* proxy = bus->GetObjectProxy( |
| kPatchPanelServiceName, dbus::ObjectPath(kPatchPanelServicePath)); |
| if (!proxy) { |
| LOG(ERROR) << "Unable to get dbus proxy for " << kPatchPanelServiceName; |
| } |
| return proxy; |
| } |
| |
| } // 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 = GetProxy(bus); |
| if (!proxy) |
| return nullptr; |
| |
| return std::make_unique<ClientImpl>(std::move(bus), proxy, |
| /*owns_bus=*/true); |
| } |
| |
| std::unique_ptr<Client> Client::New(const scoped_refptr<dbus::Bus>& bus) { |
| dbus::ObjectProxy* proxy = GetProxy(bus); |
| if (!proxy) |
| return nullptr; |
| |
| return std::make_unique<ClientImpl>(std::move(bus), proxy, |
| /*owns_bus=*/false); |
| } |
| |
| std::unique_ptr<Client> Client::New(const scoped_refptr<dbus::Bus>& bus, |
| dbus::ObjectProxy* proxy) { |
| return std::make_unique<ClientImpl>(std::move(bus), proxy, |
| /*owns_bus=*/false); |
| } |
| |
| // static |
| bool Client::IsArcGuest(Client::GuestType guest_type) { |
| switch (guest_type) { |
| case Client::GuestType::kArcContainer: |
| case Client::GuestType::kArcVm: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // static |
| std::string Client::TrafficSourceName( |
| patchpanel::Client::TrafficSource source) { |
| return patchpanel::TrafficCounter::Source_Name(ConvertTrafficSource(source)); |
| } |
| |
| // static |
| std::string Client::ProtocolName( |
| patchpanel::Client::FirewallRequestProtocol protocol) { |
| return patchpanel::ModifyPortRuleRequest::Protocol_Name( |
| ConvertFirewallRequestProtocol(protocol)); |
| } |
| |
| // static |
| std::string Client::NeighborRoleName(patchpanel::Client::NeighborRole role) { |
| return NeighborReachabilityEventSignal::Role_Name(ConvertNeighborRole(role)); |
| } |
| |
| // static |
| std::string Client::NeighborStatusName( |
| patchpanel::Client::NeighborStatus status) { |
| return NeighborReachabilityEventSignal::EventType_Name( |
| ConvertNeighborStatus(status)); |
| } |
| |
| BRILLO_EXPORT std::ostream& operator<<( |
| std::ostream& stream, const Client::NeighborReachabilityEvent& event) { |
| return stream << "{ifindex: " << event.ifindex |
| << ", ip_address: " << event.ip_addr |
| << ", role: " << Client::NeighborRoleName(event.role) |
| << ", status: " << Client::NeighborStatusName(event.status) |
| << "}"; |
| } |
| |
| } // namespace patchpanel |