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
diff --git a/host/libs/allocd/alloc_utils.h b/host/libs/allocd/alloc_utils.h
new file mode 100644
index 0000000..41b9475
--- /dev/null
+++ b/host/libs/allocd/alloc_utils.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <pwd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <optional>
+#include <sstream>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/allocd/request.h"
+
+namespace cuttlefish {
+
+
+// TODO (paulkirth): Need to have the name of the ebtables program set
+// somewhere, for now hardcode it, until resolved
+constexpr char kEbtablesName[] = "ebtables";
+
+// Wireless network prefix
+constexpr char kWirelessIp[] = "192.168.96";
+// Mobile network prefix
+constexpr char kMobileIp[] = "192.168.97";
+// permission bits for socket
+constexpr int kSocketMode = 0666;
+
+// Max ID an interface can have
+// Note: Interface names only have 2 digits in addition to the username prefix
+// Additionally limited by available netmask values in MobileNetworkName
+// Exceeding 63 would result in an overflow when calculating the netmask
+constexpr uint32_t kMaxIfaceNameId = 63;
+
+// struct for managing configuration state
+struct WirelessNetworkConfig {
+ bool has_broute_ipv4 = false;
+ bool has_broute_ipv6 = false;
+ bool has_tap = false;
+};
+
+// struct for managing configuration state
+struct GatewayConfig {
+ bool has_gateway = false;
+ bool has_dnsmasq = false;
+ bool has_iptable = false;
+};
+
+int RunExternalCommand(const std::string& command);
+std::optional<std::string> GetUserName(uid_t uid);
+
+bool AddTapIface(const std::string& name);
+bool CreateTap(const std::string& name);
+
+bool BringUpIface(const std::string& name);
+bool ShutdownIface(const std::string& name);
+
+bool DestroyIface(const std::string& name);
+bool DeleteIface(const std::string& name);
+
+bool CreateBridge(const std::string& name);
+bool DestroyBridge(const std::string& name);
+
+bool CreateEbtables(const std::string& name, bool use_ipv4);
+bool DestroyEbtables(const std::string& name, bool use_ipv4);
+bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add);
+bool EbtablesFilter(const std::string& name, bool use_ipv4, bool add);
+
+bool CreateMobileIface(const std::string& name, uint16_t id, const std::string& ipaddr);
+bool DestroyMobileIface(const std::string& name, uint16_t id, const std::string& ipaddr);
+
+bool CreateWirelessIface(const std::string& name, bool has_ipv4_bridge,
+ bool has_ipv6_bridge);
+bool DestroyWirelessIface(const std::string& name, bool has_ipv4_bridge,
+ bool use_ipv6);
+void CleanupWirelessIface(const std::string& name,
+ const WirelessNetworkConfig& config);
+
+bool IptableConfig(const std::string& network, bool add);
+
+bool LinkTapToBridge(const std::string& tap_name, const std::string& bridge_name);
+
+bool SetupBridgeGateway(const std::string& name, const std::string& ipaddr);
+void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr,
+ const GatewayConfig& config);
+
+bool CreateWirelessBridgeIface(const std::string& name);
+bool DestroyWirelessBridgeIface(const std::string& name);
+
+bool AddGateway(const std::string& name, const std::string& gateway, const std::string& netmask);
+bool DestroyGateway(const std::string& name, const std::string& gateway, const std::string& netmask);
+
+bool StartDnsmasq(const std::string& bridge_name, const std::string& gateway,
+ const std::string& dhcp_range);
+bool StopDnsmasq(const std::string& name);
+
+} // namespace cuttlefish