Add utilities needed by allocd

Contains basic operations for acquiring and releasing
resources from the system. In particular, these utilities
create and configure network interfaces.

Bug: 148823285
Test: None
Change-Id: I54ca71e6fd60764a4c32b350c35e3efcfc8c98d8
diff --git a/host/libs/allocd/alloc_utils.cpp b/host/libs/allocd/alloc_utils.cpp
new file mode 100644
index 0000000..534d591
--- /dev/null
+++ b/host/libs/allocd/alloc_utils.cpp
@@ -0,0 +1,459 @@
+#include "host/libs/allocd/alloc_utils.h"
+
+#include <cstdint>
+#include <fstream>
+
+#include "android-base/logging.h"
+
+namespace cuttlefish {
+
+int RunExternalCommand(const std::string& command) {
+  FILE* fp;
+  LOG(INFO) << "Running external command: " << command;
+  fp = popen(command.c_str(), "r");
+
+  if (fp == nullptr) {
+    LOG(WARNING) << "Error running external command";
+    return -1;
+  }
+
+  int status = pclose(fp);
+  int ret = -1;
+  if (status == -1) {
+    LOG(WARNING) << "pclose error";
+  } else {
+    if (WIFEXITED(status)) {
+      LOG(INFO) << "child process exited normally";
+      ret = WEXITSTATUS(status);
+    } else if (WIFSIGNALED(status)) {
+      LOG(WARNING) << "child process was terminated by a signal";
+    } else {
+      LOG(WARNING) << "child process did not terminate normally";
+    }
+  }
+  return ret;
+}
+
+bool AddTapIface(const std::string& name) {
+  std::stringstream ss;
+  ss << "ip tuntap add dev " << name << " mode tap group cvdnetwork vnet_hdr";
+  auto add_command = ss.str();
+  LOG(INFO) << "Create tap interface: " << add_command;
+  int status = RunExternalCommand(add_command);
+  return status == 0;
+}
+
+bool ShutdownIface(const std::string& name) {
+  std::stringstream ss;
+  ss << "ip link set dev " << name << " down";
+  auto link_command = ss.str();
+  LOG(INFO) << "Shutdown tap interface: " << link_command;
+  int status = RunExternalCommand(link_command);
+
+  return status == 0;
+}
+
+bool BringUpIface(const std::string& name) {
+  std::stringstream ss;
+  ss << "ip link set dev " << name << " up";
+  auto link_command = ss.str();
+  LOG(INFO) << "Bring up tap interface: " << link_command;
+  int status = RunExternalCommand(link_command);
+
+  return status == 0;
+}
+
+bool CreateWirelessIface(const std::string& name, bool has_ipv4_bridge,
+                         bool has_ipv6_bridge) {
+  // assume bridge exists
+
+  WirelessNetworkConfig config{false, false, false};
+
+  // TODO (paulkirth): change this to cvd-wbr, to test w/ today's debian
+  // package, this is required since the number of wireless bridges provided by
+  // the debian package has gone from 10 down to 1, but our debian packages in
+  // cloudtop are not up to date
+  auto bridge_name = "cvd-wbr-01";
+  if (!CreateTap(name)) {
+    return false;
+  }
+
+  config.has_tap = true;
+
+  if (!LinkTapToBridge(name, bridge_name)) {
+    CleanupWirelessIface(name, config);
+    return false;
+  }
+
+  if (!has_ipv4_bridge) {
+    if (!CreateEbtables(name, true)) {
+      CleanupWirelessIface(name, config);
+      return false;
+    }
+    config.has_broute_ipv4 = true;
+  }
+
+  if (!has_ipv6_bridge) {
+    if (CreateEbtables(name, false)) {
+      CleanupWirelessIface(name, config);
+      return false;
+    }
+    config.has_broute_ipv6 = true;
+  }
+
+  return true;
+}
+
+std::string MobileGatewayName(const std::string& ipaddr, uint16_t id) {
+  std::stringstream ss;
+  ss << ipaddr << "." << (4 * id + 1);
+  return ss.str();
+}
+
+std::string MobileNetworkName(const std::string& ipaddr,
+                              const std::string& netmask, uint16_t id) {
+  std::stringstream ss;
+  ss << ipaddr << "." << (4 * id) << netmask;
+  return ss.str();
+}
+
+bool CreateMobileIface(const std::string& name, uint16_t id,
+                       const std::string& ipaddr) {
+  if (id > kMaxIfaceNameId) {
+    LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id;
+    return false;
+  }
+
+  auto netmask = "/30";
+  auto gateway = MobileGatewayName(ipaddr, id);
+  auto network = MobileNetworkName(ipaddr, netmask, id);
+
+  if (!CreateTap(name)) {
+    return false;
+  }
+
+  if (!AddGateway(name, gateway, netmask)) {
+    DestroyIface(name);
+  }
+
+  if (!IptableConfig(network, true)) {
+    DestroyGateway(name, gateway, netmask);
+    DestroyIface(name);
+    return false;
+  };
+
+  return true;
+}
+
+bool DestroyMobileIface(const std::string& name, uint16_t id, const std::string& ipaddr) {
+  if (id > 63) {
+    LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id;
+    return false;
+  }
+
+  auto netmask = "/30";
+  auto gateway = MobileGatewayName(ipaddr, id);
+  auto network = MobileNetworkName(ipaddr, netmask, id);
+
+  IptableConfig(network, false);
+  DestroyGateway(name, gateway, netmask);
+  return DestroyIface(name);
+}
+
+bool AddGateway(const std::string& name, const std::string& gateway, const std::string& netmask) {
+  std::stringstream ss;
+  ss << "ip addr add " << gateway << netmask << " broadcast + dev " << name;
+  auto command = ss.str();
+  LOG(INFO) << "setup gateway: " << command;
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool DestroyGateway(const std::string& name, const std::string& gateway,
+                    const std::string& netmask) {
+  std::stringstream ss;
+  ss << "ip addr del " << gateway << netmask << " broadcast + dev " << name;
+  auto command = ss.str();
+  LOG(INFO) << "removing gateway: " << command;
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool DestroyWirelessIface(const std::string& name, bool has_ipv4_bridge,
+                          bool has_ipv6_bridge) {
+  if (!has_ipv6_bridge) {
+    DestroyEbtables(name, false);
+  }
+
+  if (!has_ipv4_bridge) {
+    DestroyEbtables(name, true);
+  }
+
+  return DestroyIface(name);
+}
+
+void CleanupWirelessIface(const std::string& name,
+                          const WirelessNetworkConfig& config) {
+  if (config.has_broute_ipv6) {
+    DestroyEbtables(name, false);
+  }
+
+  if (config.has_broute_ipv4) {
+    DestroyEbtables(name, true);
+  }
+
+  if (config.has_tap) {
+    DestroyIface(name);
+  }
+}
+
+bool CreateEbtables(const std::string& name, bool use_ipv4) {
+  return EbtablesBroute(name, use_ipv4, true) &&
+         EbtablesFilter(name, use_ipv4, true);
+}
+bool DestroyEbtables(const std::string& name, bool use_ipv4) {
+  return EbtablesBroute(name, use_ipv4, false) &&
+         EbtablesFilter(name, use_ipv4, false);
+}
+bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add) {
+  std::stringstream ss;
+  ss << kEbtablesName << " -t broute " << (add ? "-A" : "-D") << " BROUTING -p "
+     << (use_ipv4 ? "ipv4" : "ipv6") << " --in-if " << name << " -j DROP";
+  auto command = ss.str();
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool EbtablesFilter(const std::string& name, bool use_ipv4, bool add) {
+  std::stringstream ss;
+  ss << kEbtablesName << " -t filter " << (add ? "-A" : "-D") << " FORWARD -p "
+     << (use_ipv4 ? "ipv4" : "ipv6") << " --out-if " << name << " -j DROP";
+  auto command = ss.str();
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool LinkTapToBridge(const std::string& tap_name, const std::string& bridge_name) {
+  std::stringstream ss;
+  ss << "ip link set dev " << tap_name << " master " << bridge_name;
+  auto command = ss.str();
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool CreateTap(const std::string& name) {
+  LOG(INFO) << "Attempt to create tap interface: " << name;
+  if (!AddTapIface(name)) {
+    LOG(WARNING) << "Failed to create tap interface: " << name;
+    return false;
+  }
+
+  if (!BringUpIface(name)) {
+    LOG(WARNING) << "Failed to bring up tap interface: " << name;
+    DeleteIface(name);
+    return false;
+  }
+
+  return true;
+}
+
+bool DeleteIface(const std::string& name) {
+  std::stringstream ss;
+  ss << "ip link delete " << name;
+  auto link_command = ss.str();
+  LOG(INFO) << "Delete tap interface: " << link_command;
+  int status = RunExternalCommand(link_command);
+
+  return status == 0;
+}
+
+bool DestroyIface(const std::string& name) {
+  if (!ShutdownIface(name)) {
+    LOG(WARNING) << "Failed to shutdown tap interface: " << name;
+    // the interface might have already shutdown ... so ignore and try to remove
+    // the interface. In the future we could read from the pipe and handle this
+    // case more elegantly
+  }
+
+  if (!DeleteIface(name)) {
+    LOG(WARNING) << "Failed to delete tap interface: " << name;
+    return false;
+  }
+
+  return true;
+}
+
+std::optional<std::string> GetUserName(uid_t uid) {
+  passwd* pw = getpwuid(uid);
+  if (pw) {
+    std::string ret(pw->pw_name);
+    return ret;
+  }
+  return std::nullopt;
+}
+
+bool CreateBridge(const std::string& name) {
+  std::stringstream ss;
+  ss << "ip link add name " << name
+     << " type bridge forward_delay 0 stp_state 0";
+
+  auto command = ss.str();
+  LOG(INFO) << "create bridge: " << command;
+  int status = RunExternalCommand(command);
+
+  if (status != 0) {
+    return false;
+  }
+
+  return BringUpIface(name);
+}
+
+bool DestroyBridge(const std::string& name) { return DeleteIface(name); }
+
+bool SetupBridgeGateway(const std::string& bridge_name, const std::string& ipaddr) {
+  GatewayConfig config{false, false, false};
+  auto gateway = ipaddr + ".1";
+  auto netmask = "/24";
+  auto network = ipaddr + ".0" + netmask;
+  auto dhcp_range = ipaddr + ".2," + ipaddr + ".255";
+
+  if (!AddGateway(bridge_name, gateway, netmask)) {
+    return false;
+  }
+
+  config.has_gateway = true;
+
+  if (StartDnsmasq(bridge_name, gateway, dhcp_range)) {
+    CleanupBridgeGateway(bridge_name, ipaddr, config);
+    return false;
+  }
+
+  config.has_dnsmasq = true;
+
+  auto ret = IptableConfig(network, true);
+  if (!ret) {
+    CleanupBridgeGateway(bridge_name, ipaddr, config);
+    LOG(WARNING) << "Failed to setup ip tables";
+  }
+
+  return ret;
+}
+
+void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr,
+                          const GatewayConfig& config) {
+  auto gateway = ipaddr + ".1";
+  auto netmask = "/24";
+  auto network = ipaddr + ".0" + netmask;
+  auto dhcp_range = ipaddr + ".2," + ipaddr + ".255";
+
+  if (config.has_iptable) {
+    IptableConfig(network, false);
+  }
+
+  if (config.has_dnsmasq) {
+    StopDnsmasq(name);
+  }
+
+  if (config.has_gateway) {
+    DestroyGateway(name, gateway, netmask);
+  }
+}
+
+bool StartDnsmasq(const std::string& bridge_name, const std::string& gateway,
+                  const std::string& dhcp_range) {
+  auto dns_servers = "8.8.8.8,8.8.4.4";
+  auto dns6_servers = "2001:4860:4860::8888,2001:4860:4860::8844";
+  std::stringstream ss;
+
+  // clang-format off
+  ss << 
+  "dnsmasq"
+    " --port=0"
+    " --strict-order"
+    " --except-interface=lo"
+    " --interface=" << bridge_name << 
+    " --listen-address=" << gateway << 
+    " --bind-interfaces"
+    " --dhcp-range=" << dhcp_range << 
+    " --dhcp-option=\"option:dns-server," << dns_servers << "\""
+    " --dhcp-option=\"option6:dns-server," << dns6_servers << "\""
+    " --conf-file=\"\""
+    " --pid-file=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".pid"
+    " --dhcp-leasefile=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".leases"
+    " --dhcp-no-override ";
+  // clang-format on
+
+  auto command = ss.str();
+  LOG(INFO) << "start_dnsmasq: " << command;
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool StopDnsmasq(const std::string& name) {
+  std::ifstream file;
+  std::string filename = "/var/run/cuttlefish-dnsmasq-" + name + ".pid";
+  LOG(INFO) << "stopping dsnmasq for interface: " << name;
+  file.open(filename);
+  if (file.is_open()) {
+    LOG(INFO) << "dnsmasq file:" << filename
+              << " could not be opened, assume dnsmaq has already stopped";
+    return true;
+  }
+
+  std::string pid;
+  file >> pid;
+  file.close();
+  std::string command = "kill " + pid;
+  int status = RunExternalCommand(command);
+  auto ret = (status == 0);
+
+  if (ret) {
+    LOG(INFO) << "dsnmasq for:" << name << "successfully stopped";
+  } else {
+    LOG(WARNING) << "Failed to stop dsnmasq for:" << name;
+  }
+  return ret;
+}
+
+bool IptableConfig(std::string network, bool add) {
+  std::stringstream ss;
+  ss << "iptables -t nat " << (add ? "-A" : "-D") << " POSTROUTING -s "
+     << network << " -j MASQUERADE";
+
+  auto command = ss.str();
+  LOG(INFO) << "iptable_config: " << command;
+  int status = RunExternalCommand(command);
+
+  return status == 0;
+}
+
+bool CreateWirelessBridgeIface(const std::string& name) {
+  if (!CreateBridge(name)) {
+    return false;
+  }
+
+  if (!SetupBridgeGateway(name, kWirelessIp)) {
+    DestroyBridge(name);
+    return false;
+  }
+
+  return true;
+}
+
+bool DestroyWirelessBridgeIface(const std::string& name) {
+  GatewayConfig config{true, true, true};
+
+  // Don't need to check if removing some part of the config failed, we need to
+  // remove the entire interface, so just ignore any error until the end
+  CleanupBridgeGateway(name, kWirelessIp, config);
+
+  return DestroyBridge(name);
+}
+
+}  // namespace cuttlefish