blob: 8668129be8c6d56eae21de45303ed6f66a7aa95b [file] [log] [blame]
// Copyright 2018 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 "arc/vm/vsock_proxy/server_proxy.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// Needs to be included after sys/socket.h
#include <linux/un.h>
#include <linux/vm_sockets.h>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/synchronization/waitable_event.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/posix/unix_domain_socket.h>
#include <brillo/userdb_utils.h>
#include "arc/vm/vsock_proxy/file_descriptor_util.h"
#include "arc/vm/vsock_proxy/message.pb.h"
#include "arc/vm/vsock_proxy/proxy_file_system.h"
#include "arc/vm/vsock_proxy/vsock_proxy.h"
namespace arc {
namespace {
// Port for VSOCK.
constexpr unsigned int kVSockPort = 9900;
// Crosvm connects to this socket when creating a new virtwl context.
constexpr char kVirtwlSocketPath[] = "/run/arcvm/mojo/mojo-proxy.sock";
base::ScopedFD CreateVSock() {
LOG(INFO) << "Creating VSOCK...";
struct sockaddr_vm sa = {};
sa.svm_family = AF_VSOCK;
sa.svm_cid = VMADDR_CID_ANY;
sa.svm_port = kVSockPort;
base::ScopedFD fd(
socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0 /* protocol */));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to create VSOCK";
return {};
}
if (bind(fd.get(), reinterpret_cast<const struct sockaddr*>(&sa),
sizeof(sa)) == -1) {
PLOG(ERROR) << "Failed to bind a unix domain socket";
return {};
}
if (listen(fd.get(), 5 /* backlog */) == -1) {
PLOG(ERROR) << "Failed to start listening a socket";
return {};
}
LOG(INFO) << "VSOCK created.";
return fd;
}
// Sets up a socket to accept virtwl connections.
base::ScopedFD SetupVirtwlSocket() {
// Delete the socket created by a previous run if any.
if (!base::DeleteFile(base::FilePath(kVirtwlSocketPath),
false /* recursive */)) {
PLOG(ERROR) << "DeleteFile() failed " << kVirtwlSocketPath;
return {};
}
// Bind a socket to the path.
base::ScopedFD sock(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0));
if (!sock.is_valid()) {
PLOG(ERROR) << "socket() failed";
return {};
}
struct sockaddr_un unix_addr = {};
unix_addr.sun_family = AF_UNIX;
strncpy(unix_addr.sun_path, kVirtwlSocketPath, sizeof(unix_addr.sun_path));
if (bind(sock.get(), reinterpret_cast<const sockaddr*>(&unix_addr),
sizeof(unix_addr)) < 0) {
PLOG(ERROR) << "bind failed " << kVirtwlSocketPath;
return {};
}
// Make it accessible to crosvm.
uid_t uid = 0;
gid_t gid = 0;
if (!brillo::userdb::GetUserInfo("crosvm", &uid, &gid)) {
LOG(ERROR) << "Failed to get crosvm user info.";
return {};
}
if (lchown(kVirtwlSocketPath, uid, gid) != 0) {
PLOG(ERROR) << "lchown failed";
return {};
}
// Start listening on the socket.
if (listen(sock.get(), SOMAXCONN) < 0) {
PLOG(ERROR) << "listen failed";
return {};
}
return sock;
}
} // namespace
ServerProxy::ServerProxy(
scoped_refptr<base::TaskRunner> proxy_file_system_task_runner,
const base::FilePath& proxy_file_system_mount_path,
base::OnceClosure quit_closure)
: proxy_file_system_task_runner_(proxy_file_system_task_runner),
proxy_file_system_(this,
base::ThreadTaskRunnerHandle::Get(),
proxy_file_system_mount_path),
quit_closure_(std::move(quit_closure)) {}
ServerProxy::~ServerProxy() = default;
bool ServerProxy::Initialize() {
// Initialize ProxyFileSystem.
base::WaitableEvent file_system_initialized(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
bool result = false;
proxy_file_system_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](ProxyFileSystem* proxy_file_system,
base::WaitableEvent* file_system_initialized, bool* result) {
*result = proxy_file_system->Init();
file_system_initialized->Signal();
},
&proxy_file_system_, &file_system_initialized, &result));
file_system_initialized.Wait();
if (!result) {
LOG(ERROR) << "Failed to initialize ProxyFileSystem.";
return false;
}
// The connection is established as follows.
// 1) Chrome creates a socket at /run/chrome/arc_bridge.sock (in host).
// 2) Start ARCVM, then starts host proxy in host OS.
// 3) Host proxy prepares VSOCK and listens it.
// 4) ClientProxy in arcbridgeservice connects to VSOCK, and initializes
// VSockProxy, then creates /var/run/chrome/arc_bridge.sock in guest.
// 5) ArcBridgeService in arcbridgeservice connects to the guest
// arc_bridge.sock.
// 6) VSockProxy in client is notified, so send a message to request connect
// to the /run/chrome/arc_bridge.sock to host via VSOCK.
// 7) Host proxy connects as client requested, then returns its corresponding
// handle to client.
// 8) Finally, ClientProxy accept(2)s the /var/run/chrome/arc_bridge.sock,
// and register the file descriptor with the returned handle.
// Now ArcBridge connection between ARCVM and host is established.
auto vsock = CreateVSock();
if (!vsock.is_valid())
return false;
LOG(INFO) << "Start observing VSOCK";
auto accepted = AcceptSocket(vsock.get());
if (!accepted.is_valid())
return false;
// Initialize virtwl context.
virtwl_socket_ = SetupVirtwlSocket();
if (!virtwl_socket_.is_valid()) {
LOG(ERROR) << "Failed to set up virtwl socket.";
return false;
}
// Accept virtwl connection asynchronously to avoid blocking the
// initialization in case the guest proxy is old (i.e. no connection coming).
// TODO(hashimoto): Do this synchronously for proper error handling after the
// guest proxy code gets updated.
virtwl_socket_watcher_ = base::FileDescriptorWatcher::WatchReadable(
virtwl_socket_.get(),
base::BindRepeating(&ServerProxy::AcceptVirtwlConnection,
base::Unretained(this)));
vsock.reset();
LOG(INFO) << "Initial socket connection comes";
vsock_proxy_ = std::make_unique<VSockProxy>(this, std::move(accepted));
LOG(INFO) << "ServerProxy has started to work.";
return true;
}
base::ScopedFD ServerProxy::CreateProxiedRegularFile(int64_t handle) {
// Create a file descriptor which is handled by |proxy_file_system_|.
return proxy_file_system_.RegisterHandle(handle);
}
bool ServerProxy::ConvertFileDescriptorToProto(
int fd, arc_proxy::FileDescriptor* proto) {
LOG(ERROR) << "Unsupported FD type.";
return false;
}
base::ScopedFD ServerProxy::ConvertProtoToFileDescriptor(
const arc_proxy::FileDescriptor& proto) {
switch (proto.type()) {
case arc_proxy::FileDescriptor::DMABUF: {
char dummy_data = 0;
std::vector<base::ScopedFD> fds;
ssize_t size =
Recvmsg(virtwl_context_.get(), &dummy_data, sizeof(dummy_data), &fds);
if (size != sizeof(dummy_data)) {
PLOG(ERROR) << "Failed to receive a message";
return {};
}
if (fds.size() != 1) {
LOG(ERROR) << "Wrong FD size: " << fds.size();
return {};
}
vsock_proxy_->Close(proto.handle()); // Close the FD owned by guest.
return std::move(fds[0]);
}
default:
LOG(ERROR) << "Unsupported FD type: " << proto.type();
return {};
}
}
void ServerProxy::OnStopped() {
std::move(quit_closure_).Run();
}
void ServerProxy::Pread(int64_t handle,
uint64_t count,
uint64_t offset,
PreadCallback callback) {
vsock_proxy_->Pread(handle, count, offset, std::move(callback));
}
void ServerProxy::Close(int64_t handle) {
vsock_proxy_->Close(handle);
}
void ServerProxy::Fstat(int64_t handle, FstatCallback callback) {
vsock_proxy_->Fstat(handle, std::move(callback));
}
void ServerProxy::AcceptVirtwlConnection() {
virtwl_socket_watcher_.reset();
virtwl_context_ = AcceptSocket(virtwl_socket_.get());
LOG_IF(ERROR, !virtwl_context_.is_valid())
<< "Failed to accept virtwl connection";
}
} // namespace arc