blob: 6f21ca962abe33e8c145af0c7d30f313c8227460 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vm_tools/maitred/service_impl.h"
#include <arpa/inet.h>
#include <errno.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/process/launch.h>
using std::string;
namespace vm_tools {
namespace maitred {
namespace {
// Default name of the interface in the VM.
constexpr char kInterfaceName[] = "eth0";
constexpr char kLoopbackName[] = "lo";
constexpr char kHostIpPath[] = "/run/host_ip";
// How long to wait before timing out on `lxd waitready`.
constexpr int kLxdWaitreadyTimeoutSeconds = 50;
// Common environment for all LXD functionality.
const std::map<string, string> kLxdEnv = {
{"LXD_DIR", "/mnt/stateful/lxd"},
{"LXD_CONF", "/mnt/stateful/lxd_conf"},
{"LXD_UNPRIVILEGED_ONLY", "true"},
};
// Convert a 32-bit int in network byte order into a printable string.
string AddressToString(uint32_t address) {
struct in_addr in = {
.s_addr = address,
};
char buf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &in, buf, INET_ADDRSTRLEN) == nullptr) {
PLOG(ERROR) << "Failed to parse address " << address;
return string("<unknown>");
}
return string(buf);
}
// Set a network interface's flags to be up and running. Returns 0 on success,
// or the saved errno otherwise.
int EnableInterface(int sockfd, const char* ifname) {
struct ifreq ifr;
int ret;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
ret = HANDLE_EINTR(ioctl(sockfd, SIOCGIFFLAGS, &ifr));
if (ret) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to fetch flags for interface " << ifname;
return saved_errno;
}
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
ret = HANDLE_EINTR(ioctl(sockfd, SIOCSIFFLAGS, &ifr));
if (ret) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to set flags for interface " << ifname;
return saved_errno;
}
return 0;
}
} // namespace
ServiceImpl::ServiceImpl(std::unique_ptr<Init> init) : init_(std::move(init)) {}
grpc::Status ServiceImpl::ConfigureNetwork(grpc::ServerContext* ctx,
const NetworkConfigRequest* request,
EmptyMessage* response) {
static_assert(sizeof(uint32_t) == sizeof(in_addr_t),
"in_addr_t is not the same width as uint32_t");
LOG(INFO) << "Received network configuration request";
const IPv4Config& ipv4_config = request->ipv4_config();
if (ipv4_config.address() == 0) {
return grpc::Status(grpc::INVALID_ARGUMENT, "IPv4 address cannot be 0");
}
if (ipv4_config.netmask() == 0) {
return grpc::Status(grpc::INVALID_ARGUMENT, "IPv4 netmask cannot be 0");
}
if (ipv4_config.gateway() == 0) {
return grpc::Status(grpc::INVALID_ARGUMENT, "IPv4 gateway cannot be 0");
}
base::ScopedFD fd(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
if (!fd.is_valid()) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to create socket";
return grpc::Status(grpc::INTERNAL, string("failed to create socket: ") +
strerror(saved_errno));
}
// Set up the address.
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, kInterfaceName, sizeof(ifr.ifr_name));
// Holy fuck, who designed this interface? Did you know that ifr_addr and
// ifr_name are actually macros?!? For example, ifr_addr expands to
// ifr_ifru.ifru_addr and ifr_name expands to ifr_ifrn.ifrn_name. This is
// because the address, the flags, the netmask, and basically everything
// else all share the same underlying storage via a union. "Let's just put
// everything into one union. Who needs type safety anyway?". smh.
struct sockaddr_in* addr =
reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_addr);
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = static_cast<in_addr_t>(ipv4_config.address());
if (HANDLE_EINTR(ioctl(fd.get(), SIOCSIFADDR, &ifr)) != 0) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to set IPv4 address for interface " << kInterfaceName
<< " to " << AddressToString(ipv4_config.address());
return grpc::Status(grpc::INTERNAL, string("failed to set IPv4 address: ") +
strerror(saved_errno));
}
LOG(INFO) << "Set IPv4 address for interface " << kInterfaceName << " to "
<< AddressToString(ipv4_config.address());
// Set the netmask.
struct sockaddr_in* netmask =
reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_netmask);
netmask->sin_family = AF_INET;
netmask->sin_addr.s_addr = static_cast<in_addr_t>(ipv4_config.netmask());
if (HANDLE_EINTR(ioctl(fd.get(), SIOCSIFNETMASK, &ifr)) != 0) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to set IPv4 netmask for interface " << kInterfaceName
<< " to " << AddressToString(ipv4_config.netmask());
return grpc::Status(grpc::INTERNAL, string("failed to set IPv4 netmask: ") +
strerror(saved_errno));
}
LOG(INFO) << "Set IPv4 netmask for interface " << kInterfaceName << " to "
<< AddressToString(ipv4_config.netmask());
// Set the interface up and running. This needs to happen before the kernel
// will let us set the gateway.
int ret = EnableInterface(fd.get(), kInterfaceName);
if (ret) {
return grpc::Status(
grpc::INTERNAL,
string("failed to enable network interface: ") + strerror(ret));
}
LOG(INFO) << "Set interface " << kInterfaceName << " up and running";
// Bring up the loopback interface too.
ret = EnableInterface(fd.get(), kLoopbackName);
if (ret) {
return grpc::Status(
grpc::INTERNAL,
string("failed to enable loopback interface") + strerror(ret));
}
// Set the gateway.
struct rtentry route;
memset(&route, 0, sizeof(route));
struct sockaddr_in* gateway =
reinterpret_cast<struct sockaddr_in*>(&route.rt_gateway);
gateway->sin_family = AF_INET;
gateway->sin_addr.s_addr = static_cast<in_addr_t>(ipv4_config.gateway());
struct sockaddr_in* dst =
reinterpret_cast<struct sockaddr_in*>(&route.rt_dst);
dst->sin_family = AF_INET;
dst->sin_addr.s_addr = INADDR_ANY;
struct sockaddr_in* genmask =
reinterpret_cast<struct sockaddr_in*>(&route.rt_genmask);
genmask->sin_family = AF_INET;
genmask->sin_addr.s_addr = INADDR_ANY;
route.rt_flags = RTF_UP | RTF_GATEWAY;
string gateway_str = AddressToString(ipv4_config.gateway());
if (HANDLE_EINTR(ioctl(fd.get(), SIOCADDRT, &route)) != 0) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to set default IPv4 gateway for interface "
<< kInterfaceName << " to " << gateway_str;
return grpc::Status(grpc::INTERNAL, string("failed to set IPv4 gateway: ") +
strerror(saved_errno));
}
LOG(INFO) << "Set default IPv4 gateway for interface " << kInterfaceName
<< " to " << gateway_str;
// Write the host IP address to a file for LXD containers to use.
base::FilePath host_ip_path(kHostIpPath);
size_t gateway_str_len = gateway_str.size();
if (base::WriteFile(host_ip_path, gateway_str.c_str(), gateway_str_len) !=
gateway_str_len) {
LOG(ERROR) << "Failed to write host IPv4 address to file";
return grpc::Status(grpc::INTERNAL, "failed to write host IPv4 address");
}
if (!base::SetPosixFilePermissions(host_ip_path, 0644)) {
LOG(ERROR) << "Failed to set host IPv4 address file permissions";
return grpc::Status(grpc::INTERNAL,
"failed to set host IPv4 address permissions");
}
return grpc::Status::OK;
}
grpc::Status ServiceImpl::Shutdown(grpc::ServerContext* ctx,
const EmptyMessage* request,
EmptyMessage* response) {
LOG(INFO) << "Received shutdown request";
if (!init_) {
return grpc::Status(grpc::FAILED_PRECONDITION, "not running as init");
}
init_->Shutdown();
shutdown_cb_.Run();
return grpc::Status::OK;
}
grpc::Status ServiceImpl::LaunchProcess(
grpc::ServerContext* ctx,
const vm_tools::LaunchProcessRequest* request,
vm_tools::LaunchProcessResponse* response) {
LOG(INFO) << "Received request to launch process";
if (!init_) {
return grpc::Status(grpc::FAILED_PRECONDITION, "not running as init");
}
if (request->argv_size() <= 0) {
return grpc::Status(grpc::INVALID_ARGUMENT, "missing argv");
}
if (request->respawn() && request->wait_for_exit()) {
return grpc::Status(grpc::INVALID_ARGUMENT,
"respawn and wait_for_exit cannot both be true");
}
std::vector<string> argv(request->argv().begin(), request->argv().end());
std::map<string, string> env;
for (const auto& pair : request->env()) {
env[pair.first] = pair.second;
}
Init::ProcessLaunchInfo launch_info;
if (!init_->Spawn(std::move(argv), std::move(env), request->respawn(),
request->use_console(), request->wait_for_exit(),
&launch_info)) {
return grpc::Status(grpc::INTERNAL, "failed to spawn process");
}
switch (launch_info.status) {
case Init::ProcessStatus::UNKNOWN:
LOG(WARNING) << "Child process has unknown status";
response->set_status(vm_tools::UNKNOWN);
break;
case Init::ProcessStatus::EXITED:
LOG(INFO) << "Requested process " << request->argv()[0] << " exited with "
<< "status " << launch_info.code;
response->set_status(vm_tools::EXITED);
response->set_code(launch_info.code);
break;
case Init::ProcessStatus::SIGNALED:
LOG(INFO) << "Requested process " << request->argv()[0] << " killed by "
<< "signal " << launch_info.code;
response->set_status(vm_tools::SIGNALED);
response->set_code(launch_info.code);
break;
case Init::ProcessStatus::LAUNCHED:
LOG(INFO) << "Launched process " << request->argv()[0];
response->set_status(vm_tools::LAUNCHED);
break;
case Init::ProcessStatus::FAILED:
LOG(ERROR) << "Failed to launch requested process";
response->set_status(vm_tools::FAILED);
break;
}
// Return OK no matter what because the RPC itself succeeded even if there
// was an issue with launching the process.
return grpc::Status::OK;
}
grpc::Status ServiceImpl::Mount(grpc::ServerContext* ctx,
const MountRequest* request,
MountResponse* response) {
LOG(INFO) << "Received mount request";
int ret = mount(request->source().c_str(), request->target().c_str(),
request->fstype().c_str(), request->mountflags(),
request->options().c_str());
if (ret < 0) {
response->set_error(errno);
PLOG(ERROR) << "Failed to mount \"" << request->source() << "\" on \""
<< request->target() << "\"";
} else {
response->set_error(0);
LOG(INFO) << "Mounted \"" << request->source() << "\" on \""
<< request->target() << "\"";
}
return grpc::Status::OK;
}
grpc::Status ServiceImpl::StartTermina(grpc::ServerContext* ctx,
const StartTerminaRequest* request,
StartTerminaResponse* response) {
LOG(INFO) << "Received StartTermina request";
if (!init_) {
return grpc::Status(grpc::FAILED_PRECONDITION, "not running as init");
}
Init::ProcessLaunchInfo launch_info;
if (!init_->Spawn({"mkfs.btrfs", "/dev/vdb"}, kLxdEnv, false /*respawn*/,
false /*use_console*/, true /*wait_for_exit*/,
&launch_info)) {
return grpc::Status(grpc::INTERNAL, "failed to spawn mkfs.btrfs");
}
if (launch_info.status != Init::ProcessStatus::EXITED) {
return grpc::Status(grpc::INTERNAL, "mkfs.btrfs did not complete");
}
// mkfs.btrfs will fail if the disk is already formatted as btrfs.
// Optimistically continue on - if the mount fails, then return an error.
int ret =
mount("/dev/vdb", "/mnt/stateful", "btrfs", 0, "user_subvol_rm_allowed");
if (ret != 0) {
int saved_errno = errno;
PLOG(ERROR) << "Failed to mount stateful disk";
return grpc::Status(grpc::INTERNAL, string("failed to mount stateful: ") +
strerror(saved_errno));
}
if (!init_->Spawn({"lxd", "--group", "lxd"}, kLxdEnv, true /*respawn*/,
false /*use_console*/, false /*wait_for_exit*/,
&launch_info)) {
return grpc::Status(grpc::INTERNAL, "failed to spawn lxd");
}
if (launch_info.status != Init::ProcessStatus::LAUNCHED) {
return grpc::Status(grpc::INTERNAL, "lxd did not launch");
}
string timeout = std::to_string(kLxdWaitreadyTimeoutSeconds);
if (!init_->Spawn({"lxd", "waitready", "--timeout", timeout}, kLxdEnv,
false /*respawn*/, false /*use_console*/,
true /*wait_for_exit*/, &launch_info)) {
return grpc::Status(grpc::INTERNAL, "failed to spawn lxd waitready");
}
if (launch_info.status != Init::ProcessStatus::EXITED) {
return grpc::Status(grpc::INTERNAL, "lxd waitready did not complete");
} else if (launch_info.code != 0) {
return grpc::Status(grpc::INTERNAL, "lxd waitready returned non-zero");
}
string host_ip = AddressToString(request->tremplin_ipv4_address());
if (!init_->Spawn({"tremplin", "-host_addr", host_ip, "-lxd_subnet",
request->lxd_ipv4_subnet()},
kLxdEnv, true /*respawn*/, false /*use_console*/,
false /*wait_for_exit*/, &launch_info)) {
return grpc::Status(grpc::INTERNAL, "failed to spawn tremplin");
}
if (launch_info.status != Init::ProcessStatus::LAUNCHED) {
return grpc::Status(grpc::INTERNAL, "tremplin did not launch");
}
return grpc::Status::OK;
}
} // namespace maitred
} // namespace vm_tools