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