You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
483 lines
13 KiB
483 lines
13 KiB
#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
|