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