arc: Move platform2/arc/network/ to platform2/patchpanel
Next step in the arc-networkd -> patchpanel rename, this patch moves the
location of the code.
BUG=b:151879931
TEST=units,flashed image to atlas
TEST=tasts arc.PlayStore, crostini.LaunchTerminal.download
Change-Id: I1b5cf8d670e1631d46f6449b725395157bf88dde
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2115863
Tested-by: Garrick Evans <garrick@chromium.org>
Commit-Queue: Garrick Evans <garrick@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
diff --git a/patchpanel/arc_service.cc b/patchpanel/arc_service.cc
new file mode 100644
index 0000000..7bc40a6
--- /dev/null
+++ b/patchpanel/arc_service.cc
@@ -0,0 +1,836 @@
+// 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.
+
+#include "patchpanel/arc_service.h"
+
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/key_value_store.h>
+#include <chromeos/constants/vm_tools.h>
+
+#include "patchpanel/datapath.h"
+#include "patchpanel/mac_address_generator.h"
+#include "patchpanel/manager.h"
+#include "patchpanel/minijailed_process_runner.h"
+#include "patchpanel/net_util.h"
+#include "patchpanel/scoped_ns.h"
+
+namespace patchpanel {
+namespace test {
+GuestMessage::GuestType guest = GuestMessage::UNKNOWN_GUEST;
+} // namespace test
+
+namespace {
+constexpr pid_t kInvalidPID = 0;
+constexpr uint32_t kInvalidCID = 0;
+constexpr char kArcIfname[] = "arc0";
+constexpr char kArcBridge[] = "arcbr0";
+constexpr char kArcVmIfname[] = "arc1";
+constexpr char kArcVmBridge[] = "arc_br1";
+constexpr std::array<const char*, 2> kEthernetInterfacePrefixes{{"eth", "usb"}};
+constexpr std::array<const char*, 2> kWifiInterfacePrefixes{{"wlan", "mlan"}};
+constexpr std::array<const char*, 2> kCellInterfacePrefixes{{"wwan", "rmnet"}};
+
+void OneTimeSetup(const Datapath& datapath) {
+ static bool done = false;
+ if (done)
+ return;
+
+ auto& runner = datapath.runner();
+
+ // Load networking modules needed by Android that are not compiled in the
+ // kernel. Android does not allow auto-loading of kernel modules.
+ // These must succeed.
+ if (runner.modprobe_all({
+ // The netfilter modules needed by netd for iptables commands.
+ "ip6table_filter",
+ "ip6t_ipv6header",
+ "ip6t_REJECT",
+ // The xfrm modules needed for Android's ipsec APIs.
+ "xfrm4_mode_transport",
+ "xfrm4_mode_tunnel",
+ "xfrm6_mode_transport",
+ "xfrm6_mode_tunnel",
+ // The ipsec modules for AH and ESP encryption for ipv6.
+ "ah6",
+ "esp6",
+ }) != 0) {
+ LOG(ERROR) << "One or more required kernel modules failed to load."
+ << " Some Android functionality may be broken.";
+ }
+ // Optional modules.
+ if (runner.modprobe_all({
+ // This module is not available in kernels < 3.18
+ "nf_reject_ipv6",
+ // These modules are needed for supporting Chrome traffic on Android
+ // VPN which uses Android's NAT feature. Android NAT sets up
+ // iptables
+ // rules that use these conntrack modules for FTP/TFTP.
+ "nf_nat_ftp",
+ "nf_nat_tftp",
+ // The tun module is needed by the Android 464xlat clatd process.
+ "tun",
+ }) != 0) {
+ LOG(WARNING) << "One or more optional kernel modules failed to load.";
+ }
+
+ // This is only needed for CTS (b/27932574).
+ if (runner.chown("655360", "655360", "/sys/class/xt_idletimer") != 0) {
+ LOG(ERROR) << "Failed to change ownership of xt_idletimer.";
+ }
+
+ done = true;
+}
+
+bool IsArcVm() {
+ if (test::guest == GuestMessage::ARC_VM) {
+ LOG(WARNING) << "Overridden for testing";
+ return true;
+ }
+
+ const base::FilePath path("/run/chrome/is_arcvm");
+ std::string contents;
+ if (!base::ReadFileToString(path, &contents)) {
+ PLOG(ERROR) << "Could not read " << path.value();
+ }
+ return contents == "1";
+}
+
+GuestMessage::GuestType ArcGuest() {
+ if (test::guest != GuestMessage::UNKNOWN_GUEST)
+ return test::guest;
+
+ return IsArcVm() ? GuestMessage::ARC_VM : GuestMessage::ARC;
+}
+
+ArcService::InterfaceType InterfaceTypeFor(const std::string& ifname) {
+ for (const auto& prefix : kEthernetInterfacePrefixes) {
+ if (base::StartsWith(ifname, prefix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ return ArcService::InterfaceType::ETHERNET;
+ }
+ }
+ for (const auto& prefix : kWifiInterfacePrefixes) {
+ if (base::StartsWith(ifname, prefix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ return ArcService::InterfaceType::WIFI;
+ }
+ }
+ for (const auto& prefix : kCellInterfacePrefixes) {
+ if (base::StartsWith(ifname, prefix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ return ArcService::InterfaceType::CELL;
+ }
+ }
+ return ArcService::InterfaceType::UNKNOWN;
+}
+
+bool IsMulticastInterface(const std::string& ifname) {
+ if (ifname.empty()) {
+ return false;
+ }
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ // If IPv4 fails, try to open a socket using IPv6.
+ fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ LOG(ERROR) << "Unable to create socket";
+ return false;
+ }
+ }
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ);
+ if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
+ PLOG(ERROR) << "SIOCGIFFLAGS failed for " << ifname;
+ close(fd);
+ return false;
+ }
+
+ close(fd);
+ return (ifr.ifr_flags & IFF_MULTICAST);
+}
+
+// Returns the configuration for the ARC management interface used for VPN
+// forwarding, ADB-over-TCP and single-networked ARCVM.
+std::unique_ptr<Device::Config> MakeArcConfig(AddressManager* addr_mgr,
+ AddressManager::Guest guest) {
+ auto ipv4_subnet = addr_mgr->AllocateIPv4Subnet(guest);
+ if (!ipv4_subnet) {
+ LOG(ERROR) << "Subnet already in use or unavailable";
+ return nullptr;
+ }
+ auto host_ipv4_addr = ipv4_subnet->AllocateAtOffset(0);
+ if (!host_ipv4_addr) {
+ LOG(ERROR) << "Bridge address already in use or unavailable";
+ return nullptr;
+ }
+ auto guest_ipv4_addr = ipv4_subnet->AllocateAtOffset(1);
+ if (!guest_ipv4_addr) {
+ LOG(ERROR) << "ARC address already in use or unavailable";
+ return nullptr;
+ }
+
+ return std::make_unique<Device::Config>(
+ addr_mgr->GenerateMacAddress(IsArcVm() ? 1 : kAnySubnetIndex),
+ std::move(ipv4_subnet), std::move(host_ipv4_addr),
+ std::move(guest_ipv4_addr));
+}
+
+} // namespace
+
+ArcService::ArcService(ShillClient* shill_client,
+ Datapath* datapath,
+ AddressManager* addr_mgr,
+ TrafficForwarder* forwarder,
+ bool enable_arcvm_multinet)
+ : shill_client_(shill_client),
+ datapath_(datapath),
+ addr_mgr_(addr_mgr),
+ forwarder_(forwarder),
+ enable_arcvm_multinet_(enable_arcvm_multinet) {
+ AllocateAddressConfigs();
+ shill_client_->RegisterDevicesChangedHandler(
+ base::Bind(&ArcService::OnDevicesChanged, weak_factory_.GetWeakPtr()));
+ shill_client_->ScanDevices(
+ base::Bind(&ArcService::OnDevicesChanged, weak_factory_.GetWeakPtr()));
+ shill_client_->RegisterDefaultInterfaceChangedHandler(base::Bind(
+ &ArcService::OnDefaultInterfaceChanged, weak_factory_.GetWeakPtr()));
+}
+
+ArcService::~ArcService() {
+ if (impl_) {
+ Stop(impl_->id());
+ }
+}
+
+void ArcService::AllocateAddressConfigs() {
+ configs_.clear();
+ // The first usable subnet is the "other" ARC device subnet.
+ // TODO(garrick): This can be removed and ARC_NET will be widened once ARCVM
+ // switches over to use .0/30.
+ const bool is_arcvm = IsArcVm();
+ AddressManager::Guest alloc =
+ is_arcvm ? AddressManager::Guest::ARC : AddressManager::Guest::VM_ARC;
+ // As a temporary workaround, for ARCVM, allocate fixed MAC addresses.
+ uint8_t mac_addr_index = 2;
+ // Allocate 2 subnets each for Ethernet and WiFi and 1 for LTE WAN interfaces.
+ for (const auto itype :
+ {InterfaceType::ETHERNET, InterfaceType::ETHERNET, InterfaceType::WIFI,
+ InterfaceType::WIFI, InterfaceType::CELL}) {
+ auto ipv4_subnet = addr_mgr_->AllocateIPv4Subnet(alloc);
+ if (!ipv4_subnet) {
+ LOG(ERROR) << "Subnet already in use or unavailable";
+ continue;
+ }
+ // For here out, use the same slices.
+ alloc = AddressManager::Guest::ARC_NET;
+ auto host_ipv4_addr = ipv4_subnet->AllocateAtOffset(0);
+ if (!host_ipv4_addr) {
+ LOG(ERROR) << "Bridge address already in use or unavailable";
+ continue;
+ }
+ auto guest_ipv4_addr = ipv4_subnet->AllocateAtOffset(1);
+ if (!guest_ipv4_addr) {
+ LOG(ERROR) << "ARC address already in use or unavailable";
+ continue;
+ }
+
+ MacAddress mac_addr = is_arcvm
+ ? addr_mgr_->GenerateMacAddress(mac_addr_index++)
+ : addr_mgr_->GenerateMacAddress();
+
+ configs_[itype].emplace_back(std::make_unique<Device::Config>(
+ mac_addr, std::move(ipv4_subnet), std::move(host_ipv4_addr),
+ std::move(guest_ipv4_addr)));
+ }
+}
+
+std::vector<Device::Config*> ArcService::ReallocateAddressConfigs() {
+ std::vector<std::string> existing_devices;
+ for (const auto& d : devices_) {
+ existing_devices.emplace_back(d.first);
+ }
+ for (const auto& d : existing_devices) {
+ RemoveDevice(d);
+ }
+ AllocateAddressConfigs();
+ std::vector<Device::Config*> configs;
+ if (enable_arcvm_multinet_) {
+ for (const auto& kv : configs_)
+ for (const auto& c : kv.second)
+ configs.push_back(c.get());
+ }
+ for (const auto& d : existing_devices) {
+ AddDevice(d);
+ }
+ return configs;
+}
+
+std::unique_ptr<Device::Config> ArcService::AcquireConfig(
+ const std::string& ifname) {
+ auto itype = InterfaceTypeFor(ifname);
+ if (itype == InterfaceType::UNKNOWN) {
+ LOG(ERROR) << "Unsupported interface: " << ifname;
+ return nullptr;
+ }
+
+ auto& configs = configs_[itype];
+ if (configs.empty()) {
+ LOG(ERROR) << "No more addresses available. Cannot make device for "
+ << ifname;
+ return nullptr;
+ }
+ std::unique_ptr<Device::Config> config;
+ config = std::move(configs.front());
+ configs.pop_front();
+ return config;
+}
+
+void ArcService::ReleaseConfig(const std::string& ifname,
+ std::unique_ptr<Device::Config> config) {
+ auto itype = InterfaceTypeFor(ifname);
+ if (itype == InterfaceType::UNKNOWN) {
+ LOG(ERROR) << "Unsupported interface: " << ifname;
+ return;
+ }
+
+ configs_[itype].push_front(std::move(config));
+}
+
+bool ArcService::Start(uint32_t id) {
+ if (impl_) {
+ uint32_t prev_id;
+ if (impl_->IsStarted(&prev_id)) {
+ LOG(WARNING) << "Already running - did something crash?"
+ << " Stopping and restarting...";
+ Stop(prev_id);
+ }
+ }
+
+ auto configs = ReallocateAddressConfigs();
+ const auto guest = ArcGuest();
+ if (guest == GuestMessage::ARC_VM) {
+ impl_ = std::make_unique<VmImpl>(shill_client_, datapath_, addr_mgr_,
+ forwarder_, configs);
+ } else {
+ impl_ = std::make_unique<ContainerImpl>(datapath_, addr_mgr_, forwarder_,
+ guest);
+ }
+ if (!impl_->Start(id)) {
+ impl_.reset();
+ return false;
+ }
+
+ // Start already known Shill <-> ARC mapped devices.
+ for (const auto& d : devices_)
+ StartDevice(d.second.get());
+
+ return true;
+}
+
+void ArcService::Stop(uint32_t id) {
+ // Stop Shill <-> ARC mapped devices.
+ for (const auto& d : devices_)
+ StopDevice(d.second.get());
+
+ if (impl_) {
+ impl_->Stop(id);
+ impl_.reset();
+ }
+}
+
+void ArcService::OnDevicesChanged(const std::set<std::string>& added,
+ const std::set<std::string>& removed) {
+ for (const std::string& name : removed)
+ RemoveDevice(name);
+
+ for (const std::string& name : added)
+ AddDevice(name);
+}
+
+void ArcService::AddDevice(const std::string& ifname) {
+ if (ifname.empty())
+ return;
+
+ if (devices_.find(ifname) != devices_.end()) {
+ LOG(DFATAL) << "Attemping to add already tracked device: " << ifname;
+ return;
+ }
+
+ auto itype = InterfaceTypeFor(ifname);
+ Device::Options opts{
+ .fwd_multicast = IsMulticastInterface(ifname),
+ // TODO(crbug/726815) Also enable |ipv6_enabled| for cellular networks
+ // once IPv6 is enabled on cellular networks in shill.
+ .ipv6_enabled =
+ (itype == InterfaceType::ETHERNET || itype == InterfaceType::WIFI),
+ };
+
+ auto config = AcquireConfig(ifname);
+ if (!config) {
+ LOG(ERROR) << "Cannot add device for " << ifname;
+ return;
+ }
+
+ std::string host_ifname = base::StringPrintf("arc_%s", ifname.c_str());
+ auto device = std::make_unique<Device>(ifname, host_ifname, ifname,
+ std::move(config), opts);
+
+ StartDevice(device.get());
+ devices_.emplace(ifname, std::move(device));
+}
+
+void ArcService::StartDevice(Device* device) {
+ if (!impl_ || !impl_->IsStarted())
+ return;
+
+ // For now, only start devices for ARC++.
+ if (impl_->guest() != GuestMessage::ARC)
+ return;
+
+ const auto& config = device->config();
+
+ LOG(INFO) << "Adding device " << device->phys_ifname()
+ << " bridge: " << device->host_ifname()
+ << " guest_iface: " << device->guest_ifname();
+
+ // Create the bridge.
+ if (!datapath_->AddBridge(device->host_ifname(), config.host_ipv4_addr(),
+ 30)) {
+ LOG(ERROR) << "Failed to setup arc bridge: " << device->host_ifname();
+ return;
+ }
+
+ // Set up iptables.
+ if (!datapath_->AddInboundIPv4DNAT(
+ device->phys_ifname(), IPv4AddressToString(config.guest_ipv4_addr())))
+ LOG(ERROR) << "Failed to configure ingress traffic rules for "
+ << device->phys_ifname();
+
+ if (!datapath_->AddOutboundIPv4(device->host_ifname()))
+ LOG(ERROR) << "Failed to configure egress traffic rules for "
+ << device->phys_ifname();
+
+ if (!impl_->OnStartDevice(device)) {
+ LOG(ERROR) << "Failed to start device " << device->phys_ifname();
+ }
+}
+
+void ArcService::RemoveDevice(const std::string& ifname) {
+ const auto it = devices_.find(ifname);
+ if (it == devices_.end()) {
+ LOG(WARNING) << "Unknown device: " << ifname;
+ return;
+ }
+
+ // If the container is down, this call does nothing.
+ StopDevice(it->second.get());
+
+ ReleaseConfig(ifname, it->second->release_config());
+ devices_.erase(it);
+}
+
+void ArcService::StopDevice(Device* device) {
+ if (!impl_ || !impl_->IsStarted())
+ return;
+
+ // For now, devices are only started for ARC++.
+ if (impl_->guest() != GuestMessage::ARC)
+ return;
+
+ impl_->OnStopDevice(device);
+
+ const auto& config = device->config();
+
+ LOG(INFO) << "Removing device " << device->phys_ifname()
+ << " bridge: " << device->host_ifname()
+ << " guest_iface: " << device->guest_ifname();
+
+ datapath_->RemoveOutboundIPv4(device->host_ifname());
+ datapath_->RemoveInboundIPv4DNAT(
+ device->phys_ifname(), IPv4AddressToString(config.guest_ipv4_addr()));
+
+ datapath_->RemoveBridge(device->host_ifname());
+}
+
+void ArcService::OnDefaultInterfaceChanged(const std::string& new_ifname,
+ const std::string& prev_ifname) {
+ if (impl_)
+ impl_->OnDefaultInterfaceChanged(new_ifname, prev_ifname);
+}
+
+std::vector<const Device::Config*> ArcService::GetDeviceConfigs() const {
+ if (impl_)
+ return impl_->GetDeviceConfigs();
+
+ return {};
+}
+
+// ARC++ specific functions.
+
+ArcService::ContainerImpl::ContainerImpl(Datapath* datapath,
+ AddressManager* addr_mgr,
+ TrafficForwarder* forwarder,
+ GuestMessage::GuestType guest)
+ : pid_(kInvalidPID),
+ datapath_(datapath),
+ addr_mgr_(addr_mgr),
+ forwarder_(forwarder),
+ guest_(guest) {
+ OneTimeSetup(*datapath_);
+}
+
+GuestMessage::GuestType ArcService::ContainerImpl::guest() const {
+ return guest_;
+}
+
+uint32_t ArcService::ContainerImpl::id() const {
+ return pid_;
+}
+
+bool ArcService::ContainerImpl::Start(uint32_t pid) {
+ // This could happen if something crashes and the stop signal is not sent.
+ // It can probably be addressed by stopping and restarting the service.
+ if (pid_ != kInvalidPID)
+ return false;
+
+ if (pid == kInvalidPID) {
+ LOG(ERROR) << "Cannot start service - invalid container PID";
+ return false;
+ }
+ pid_ = pid;
+
+ Device::Options opts{
+ .fwd_multicast = false,
+ .ipv6_enabled = false,
+ };
+ auto config = MakeArcConfig(addr_mgr_, AddressManager::Guest::ARC);
+
+ // Create the bridge.
+ // Per crbug/1008686 this device cannot be deleted and then re-added.
+ // So instead of removing the bridge when the service stops, bring down the
+ // device instead and re-up it on restart.
+ if (!datapath_->AddBridge(kArcBridge, config->host_ipv4_addr(), 30) &&
+ !datapath_->MaskInterfaceFlags(kArcBridge, IFF_UP)) {
+ LOG(ERROR) << "Failed to bring up arc bridge: " << kArcBridge;
+ return false;
+ }
+
+ arc_device_ = std::make_unique<Device>(kArcIfname, kArcBridge, kArcIfname,
+ std::move(config), opts);
+
+ OnStartDevice(arc_device_.get());
+
+ LOG(INFO) << "ARC++ network service started {pid: " << pid_ << "}";
+ return true;
+}
+
+void ArcService::ContainerImpl::Stop(uint32_t /*pid*/) {
+ if (!IsStarted())
+ return;
+
+ // Per crbug/1008686 this device cannot be deleted and then re-added.
+ // So instead of removing the bridge, bring it down and mark it. This will
+ // allow us to detect if the device is re-added in case of a crash restart
+ // and do the right thing.
+ if (arc_device_) {
+ OnStopDevice(arc_device_.get());
+ if (!datapath_->MaskInterfaceFlags(kArcBridge, IFF_DEBUG, IFF_UP))
+ LOG(ERROR) << "Failed to bring down arc bridge "
+ << "- it may not restart correctly";
+ }
+
+ LOG(INFO) << "ARC++ network service stopped {pid: " << pid_ << "}";
+ pid_ = kInvalidPID;
+}
+
+bool ArcService::ContainerImpl::IsStarted(uint32_t* pid) const {
+ if (pid)
+ *pid = pid_;
+
+ return pid_ != kInvalidPID;
+}
+
+bool ArcService::ContainerImpl::OnStartDevice(Device* device) {
+ LOG(INFO) << "Starting device " << device->phys_ifname()
+ << " bridge: " << device->host_ifname()
+ << " guest_iface: " << device->guest_ifname() << " pid: " << pid_;
+
+ // Set up the virtual pair inside the container namespace.
+ const std::string veth_ifname = ArcVethHostName(device->guest_ifname());
+ const auto& config = device->config();
+ if (!datapath_->ConnectVethPair(pid_, veth_ifname, device->guest_ifname(),
+ config.mac_addr(), config.guest_ipv4_addr(),
+ 30, device->options().fwd_multicast)) {
+ LOG(ERROR) << "Cannot create virtual link for device "
+ << device->phys_ifname();
+ return false;
+ }
+ if (!datapath_->AddToBridge(device->host_ifname(), veth_ifname)) {
+ datapath_->RemoveInterface(veth_ifname);
+ LOG(ERROR) << "Failed to bridge interface " << veth_ifname;
+ return false;
+ }
+
+ if (device != arc_device_.get()) {
+ forwarder_->StartForwarding(device->phys_ifname(), device->host_ifname(),
+ device->options().ipv6_enabled,
+ device->options().fwd_multicast);
+ } else {
+ // Signal the container that the network device is ready.
+ datapath_->runner().WriteSentinelToContainer(pid_);
+ }
+
+ return true;
+}
+
+void ArcService::ContainerImpl::OnStopDevice(Device* device) {
+ LOG(INFO) << "Stopping device " << device->phys_ifname()
+ << " bridge: " << device->host_ifname()
+ << " guest_iface: " << device->guest_ifname() << " pid: " << pid_;
+
+ if (device != arc_device_.get())
+ forwarder_->StopForwarding(device->phys_ifname(), device->host_ifname(),
+ device->options().ipv6_enabled,
+ device->options().fwd_multicast);
+
+ datapath_->RemoveInterface(ArcVethHostName(device->phys_ifname()));
+}
+
+void ArcService::ContainerImpl::OnDefaultInterfaceChanged(
+ const std::string& new_ifname, const std::string& prev_ifname) {}
+
+// VM specific functions
+
+ArcService::VmImpl::VmImpl(ShillClient* shill_client,
+ Datapath* datapath,
+ AddressManager* addr_mgr,
+ TrafficForwarder* forwarder,
+ const std::vector<Device::Config*>& configs)
+ : cid_(kInvalidCID),
+ shill_client_(shill_client),
+ datapath_(datapath),
+ addr_mgr_(addr_mgr),
+ forwarder_(forwarder),
+ configs_(configs) {}
+
+GuestMessage::GuestType ArcService::VmImpl::guest() const {
+ return GuestMessage::ARC_VM;
+}
+
+uint32_t ArcService::VmImpl::id() const {
+ return cid_;
+}
+
+std::vector<const Device::Config*> ArcService::VmImpl::GetDeviceConfigs()
+ const {
+ std::vector<const Device::Config*> configs;
+ for (const auto* c : configs_)
+ configs.emplace_back(c);
+
+ return configs;
+}
+
+bool ArcService::VmImpl::Start(uint32_t cid) {
+ // This can happen if concierge crashes and doesn't send the vm down RPC.
+ // It can probably be addressed by stopping and restarting the service.
+ if (cid_ != kInvalidCID)
+ return false;
+
+ if (cid == kInvalidCID) {
+ LOG(ERROR) << "Invalid VM cid " << cid;
+ return false;
+ }
+ cid_ = cid;
+
+ Device::Options opts{
+ .fwd_multicast = true,
+ .ipv6_enabled = true,
+ };
+ auto arc_config = MakeArcConfig(addr_mgr_, AddressManager::Guest::VM_ARC);
+ configs_.insert(configs_.begin(), arc_config.get());
+
+ // Allocate TAP devices for all configs.
+ for (auto* config : configs_) {
+ auto mac = config->mac_addr();
+ auto tap =
+ datapath_->AddTAP("" /* auto-generate name */, &mac,
+ nullptr /* no ipv4 subnet */, vm_tools::kCrosVmUser);
+ if (tap.empty()) {
+ LOG(ERROR) << "Failed to create TAP device";
+ continue;
+ }
+
+ config->set_tap_ifname(tap);
+ }
+
+ arc_device_ = std::make_unique<Device>(
+ kArcVmIfname, kArcVmBridge, kArcVmIfname, std::move(arc_config), opts);
+ // Create the bridge.
+ if (!datapath_->AddBridge(kArcVmBridge,
+ arc_device_->config().host_ipv4_addr(), 30)) {
+ LOG(ERROR) << "Failed to setup arc bridge for device " << *arc_device_;
+ return false;
+ }
+ OnStartDevice(arc_device_.get());
+
+ LOG(INFO) << "ARCVM network service started {cid: " << cid_ << "}";
+ return true;
+}
+
+void ArcService::VmImpl::Stop(uint32_t cid) {
+ if (cid_ != cid) {
+ LOG(ERROR) << "Mismatched ARCVM CIDs " << cid_ << " != " << cid;
+ return;
+ }
+
+ for (auto* config : configs_) {
+ const auto& tap = config->tap_ifname();
+ if (!tap.empty()) {
+ datapath_->RemoveInterface(tap);
+ config->set_tap_ifname("");
+ }
+ }
+
+ OnStopDevice(arc_device_.get());
+ datapath_->RemoveBridge(kArcVmBridge);
+ arc_device_.reset();
+
+ LOG(INFO) << "ARCVM network service stopped {cid: " << cid_ << "}";
+ cid_ = kInvalidCID;
+}
+
+bool ArcService::VmImpl::IsStarted(uint32_t* cid) const {
+ if (cid)
+ *cid = cid_;
+
+ return cid_ != kInvalidCID;
+}
+
+bool ArcService::VmImpl::OnStartDevice(Device* device) {
+ // TODO(garrick): Remove once ARCVM P is gone.
+ if (device == arc_device_.get() && !IsMultinetEnabled())
+ return OnStartArcPDevice();
+
+ std::string tap;
+ for (auto* config : configs_) {
+ if (config == &device->config()) {
+ tap = config->tap_ifname();
+ break;
+ }
+ }
+ if (tap.empty()) {
+ LOG(ERROR) << "No TAP device for: " << *device;
+ return false;
+ }
+
+ LOG(INFO) << "Starting device " << *device << " cid: " << cid_;
+
+ if (!datapath_->AddToBridge(device->host_ifname(), tap)) {
+ LOG(ERROR) << "Failed to bridge TAP device " << tap;
+ datapath_->RemoveInterface(tap);
+ return false;
+ }
+
+ if (device != arc_device_.get())
+ forwarder_->StartForwarding(device->phys_ifname(), device->host_ifname(),
+ device->options().ipv6_enabled,
+ device->options().fwd_multicast);
+
+ return true;
+}
+
+bool ArcService::VmImpl::OnStartArcPDevice() {
+ LOG(INFO) << "Starting device " << *arc_device_ << " cid: " << cid_;
+
+ if (!datapath_->AddToBridge(kArcVmBridge,
+ arc_device_->config().tap_ifname())) {
+ LOG(ERROR) << "Failed to bridge TAP device " << *arc_device_;
+ return false;
+ }
+
+ // Setup the iptables.
+ if (!datapath_->AddLegacyIPv4DNAT(
+ IPv4AddressToString(arc_device_->config().guest_ipv4_addr())))
+ LOG(ERROR) << "Failed to configure ARC traffic rules";
+
+ if (!datapath_->AddOutboundIPv4(kArcVmBridge))
+ LOG(ERROR) << "Failed to configure egress traffic rules";
+
+ OnDefaultInterfaceChanged(shill_client_->default_interface(),
+ "" /*previous*/);
+
+ return true;
+}
+
+void ArcService::VmImpl::OnStopDevice(Device* device) {
+ // TODO(garrick): Remove once ARCVM P is gone.
+ if (device == arc_device_.get() && !IsMultinetEnabled())
+ return OnStopArcPDevice();
+
+ LOG(INFO) << "Stopping device " << *device << " cid: " << cid_;
+
+ if (device != arc_device_.get())
+ forwarder_->StopForwarding(device->phys_ifname(), device->host_ifname(),
+ device->options().ipv6_enabled,
+ device->options().fwd_multicast);
+
+ for (auto* config : configs_) {
+ if (config == &device->config()) {
+ config->set_tap_ifname("");
+ break;
+ }
+ }
+}
+
+void ArcService::VmImpl::OnStopArcPDevice() {
+ LOG(INFO) << "Stopping device " << *arc_device_.get() << " cid: " << cid_;
+
+ datapath_->RemoveOutboundIPv4(kArcVmBridge);
+ datapath_->RemoveLegacyIPv4DNAT();
+
+ OnDefaultInterfaceChanged("" /*new_ifname*/,
+ shill_client_->default_interface());
+
+ arc_device_->config().set_tap_ifname("");
+}
+
+void ArcService::VmImpl::OnDefaultInterfaceChanged(
+ const std::string& new_ifname, const std::string& prev_ifname) {
+ if (!IsStarted() || IsMultinetEnabled())
+ return;
+
+ forwarder_->StopForwarding(prev_ifname, kArcVmBridge, true /*ipv6*/,
+ true /*multicast*/);
+
+ datapath_->RemoveLegacyIPv4InboundDNAT();
+
+ // If a new default interface was given, then re-enable with that.
+ if (!new_ifname.empty()) {
+ datapath_->AddLegacyIPv4InboundDNAT(new_ifname);
+ forwarder_->StartForwarding(new_ifname, kArcVmBridge, true /*ipv6*/,
+ true /*multicast*/);
+ }
+}
+
+bool ArcService::VmImpl::IsMultinetEnabled() const {
+ return configs_.size() > 1;
+}
+
+} // namespace patchpanel