| #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 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<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(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 |