#include "host/libs/allocd/alloc_utils.h" #include #include #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 CreateEthernetIface(const std::string& name, const std::string& bridge_name, bool has_ipv4_bridge, bool has_ipv6_bridge, bool use_ebtables_legacy) { // assume bridge exists EthernetNetworkConfig config{false, false, false}; if (!CreateTap(name)) { return false; } config.has_tap = true; if (!LinkTapToBridge(name, bridge_name)) { CleanupEthernetIface(name, config); return false; } if (!has_ipv4_bridge) { if (!CreateEbtables(name, true, use_ebtables_legacy)) { CleanupEthernetIface(name, config); return false; } config.has_broute_ipv4 = true; } if (!has_ipv6_bridge) { if (CreateEbtables(name, false, use_ebtables_legacy)) { CleanupEthernetIface(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 DestroyEthernetIface(const std::string& name, bool has_ipv4_bridge, bool has_ipv6_bridge, bool use_ebtables_legacy) { if (!has_ipv6_bridge) { DestroyEbtables(name, false, use_ebtables_legacy); } if (!has_ipv4_bridge) { DestroyEbtables(name, true, use_ebtables_legacy); } return DestroyIface(name); } void CleanupEthernetIface(const std::string& name, const EthernetNetworkConfig& config) { if (config.has_broute_ipv6) { DestroyEbtables(name, false, config.use_ebtables_legacy); } if (config.has_broute_ipv4) { DestroyEbtables(name, true, config.use_ebtables_legacy); } if (config.has_tap) { DestroyIface(name); } } bool CreateEbtables(const std::string& name, bool use_ipv4, bool use_ebtables_legacy) { return EbtablesBroute(name, use_ipv4, true, use_ebtables_legacy) && EbtablesFilter(name, use_ipv4, true, use_ebtables_legacy); } bool DestroyEbtables(const std::string& name, bool use_ipv4, bool use_ebtables_legacy) { return EbtablesBroute(name, use_ipv4, false, use_ebtables_legacy) && EbtablesFilter(name, use_ipv4, false, use_ebtables_legacy); } bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add, bool use_ebtables_legacy) { std::stringstream ss; // we don't know the name of the ebtables program, but since we're going to // exec this program name, make sure they can only choose between the two // options we currently support, and not something they can overwrite if (use_ebtables_legacy) { ss << kEbtablesLegacyName; } else { ss << kEbtablesName; } ss << " -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, bool use_ebtables_legacy) { std::stringstream ss; if (use_ebtables_legacy) { ss << kEbtablesLegacyName; } else { ss << kEbtablesName; } ss << " -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 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(const 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 CreateEthernetBridgeIface(const std::string& name, const std::string& ipaddr) { if (!CreateBridge(name)) { return false; } if (!SetupBridgeGateway(name, ipaddr)) { DestroyBridge(name); return false; } return true; } bool DestroyEthernetBridgeIface(const std::string& name, const std::string& ipaddr) { 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, ipaddr, config); return DestroyBridge(name); } } // namespace cuttlefish