blob: 2e3158ec580325c8cf916920f1161d7961d5cc27 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "src/virtualization/bin/host_vsock/host_vsock_endpoint.h"
#include <lib/async/cpp/time.h>
#include <lib/async/default.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
using ::fuchsia::virtualization::HostVsockAcceptor_Accept_Result;
using ::fuchsia::virtualization::HostVsockConnector_Connect_Response;
using ::fuchsia::virtualization::HostVsockConnector_Connect_Result;
using ::fuchsia::virtualization::HostVsockEndpoint_Connect_Result;
using ::fuchsia::virtualization::HostVsockEndpoint_Listen_Result;
HostVsockEndpoint::HostVsockEndpoint(async_dispatcher_t* dispatcher,
AcceptorProvider acceptor_provider)
: dispatcher_(dispatcher), acceptor_provider_(std::move(acceptor_provider)) {}
void HostVsockEndpoint::AddBinding(
fidl::InterfaceRequest<fuchsia::virtualization::HostVsockEndpoint> request) {
bindings_.AddBinding(this, std::move(request));
}
void HostVsockEndpoint::Connect(
uint32_t src_cid, uint32_t src_port, uint32_t cid, uint32_t port,
fuchsia::virtualization::HostVsockConnector::ConnectCallback callback) {
if (cid == fuchsia::virtualization::HOST_CID) {
// Guest to host connection.
auto it = listeners_.find(port);
if (it == listeners_.end()) {
callback(HostVsockConnector_Connect_Result::WithErr(ZX_ERR_CONNECTION_REFUSED));
return;
}
it->second->Accept(
src_cid, src_port, port,
[callback = std::move(callback)](HostVsockAcceptor_Accept_Result result) {
callback(
result.is_response()
? HostVsockConnector_Connect_Result::WithResponse(
HostVsockConnector_Connect_Response(std::move(result.response().socket)))
: HostVsockConnector_Connect_Result::WithErr(std::move(result.err())));
});
} else {
// Guest to guest connection.
fuchsia::virtualization::GuestVsockAcceptor* acceptor = acceptor_provider_(cid);
if (acceptor == nullptr) {
callback(HostVsockConnector_Connect_Result::WithErr(ZX_ERR_CONNECTION_REFUSED));
return;
}
// Use a socket for direct guest to guest communication.
zx::socket h1, h2;
zx_status_t status = zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2);
if (status != ZX_OK) {
callback(HostVsockConnector_Connect_Result::WithErr(ZX_ERR_CONNECTION_REFUSED));
return;
}
acceptor->Accept(
src_cid, src_port, port, std::move(h1),
[callback = std::move(callback), h2 = std::move(h2)](
fuchsia::virtualization::GuestVsockAcceptor_Accept_Result result) mutable {
if (result.is_err()) {
callback(HostVsockConnector_Connect_Result::WithErr(std::move(result.err())));
} else {
callback(HostVsockConnector_Connect_Result::WithResponse(
HostVsockConnector_Connect_Response(std::move(h2))));
}
});
}
}
void HostVsockEndpoint::Listen(
uint32_t port, fidl::InterfaceHandle<fuchsia::virtualization::HostVsockAcceptor> acceptor,
ListenCallback callback) {
if (port_bitmap_.GetOne(port)) {
callback(HostVsockEndpoint_Listen_Result::WithErr(ZX_ERR_ALREADY_BOUND));
return;
}
bool inserted;
auto acceptor_ptr = acceptor.Bind();
acceptor_ptr.set_error_handler([this, port](zx_status_t status) {
port_bitmap_.ClearOne(port);
listeners_.erase(port);
});
std::tie(std::ignore, inserted) = listeners_.emplace(port, std::move(acceptor_ptr));
if (!inserted) {
callback(HostVsockEndpoint_Listen_Result::WithErr(ZX_ERR_ALREADY_BOUND));
return;
}
port_bitmap_.SetOne(port);
callback(HostVsockEndpoint_Listen_Result::WithResponse({}));
}
void HostVsockEndpoint::Connect(
uint32_t cid, uint32_t port, zx::socket socket,
fuchsia::virtualization::HostVsockEndpoint::ConnectCallback callback) {
if (cid == fuchsia::virtualization::HOST_CID) {
FX_LOGS(ERROR) << "Attempt to connect to host service from host";
callback(HostVsockEndpoint_Connect_Result::WithErr(ZX_ERR_CONNECTION_REFUSED));
return;
}
fuchsia::virtualization::GuestVsockAcceptor* acceptor = acceptor_provider_(cid);
if (acceptor == nullptr) {
callback(HostVsockEndpoint_Connect_Result::WithErr(ZX_ERR_CONNECTION_REFUSED));
return;
}
uint32_t src_port;
zx_status_t status = AllocEphemeralPort(&src_port);
if (status != ZX_OK) {
callback(HostVsockEndpoint_Connect_Result::WithErr(std::move(status)));
return;
}
// Get access to the guests.
acceptor->Accept(fuchsia::virtualization::HOST_CID, src_port, port, std::move(socket),
[this, src_port, callback = std::move(callback)](
fuchsia::virtualization::GuestVsockAcceptor_Accept_Result result) mutable {
ConnectCallback(result.is_err() ? result.err() : ZX_OK, src_port,
std::move(callback));
});
}
void HostVsockEndpoint::ConnectCallback(
zx_status_t status, uint32_t src_port,
fuchsia::virtualization::HostVsockEndpoint::ConnectCallback callback) {
if (status != ZX_OK) {
FreeEphemeralPort(src_port);
callback(HostVsockEndpoint_Connect_Result::WithErr(std::move(status)));
} else {
callback(HostVsockEndpoint_Connect_Result::WithResponse({}));
}
}
void HostVsockEndpoint::OnShutdown(uint32_t port) {
// If there are no listeners for this port then it was ephemeral and should
// free it.
if (listeners_.find(port) == listeners_.end()) {
FreeEphemeralPort(port);
}
}
zx_status_t HostVsockEndpoint::AllocEphemeralPort(uint32_t* port) {
// Remove ephemeral ports that have been in quarantine long enough.
zx::time now = async::Now(dispatcher_);
while (!quarantined_ports_.empty() && now >= quarantined_ports_.front().available_time) {
zx_status_t status = port_bitmap_.ClearOne(quarantined_ports_.front().port);
FX_DCHECK(status == ZX_OK);
quarantined_ports_.pop_front();
}
// Find an ephemeral port.
uint32_t value;
zx_status_t status = port_bitmap_.Find(false, kFirstEphemeralPort, kLastEphemeralPort, 1, &value);
if (status != ZX_OK) {
return ZX_ERR_NO_RESOURCES;
}
*port = value;
return port_bitmap_.SetOne(value);
}
void HostVsockEndpoint::FreeEphemeralPort(uint32_t port) {
FX_DCHECK(port_bitmap_.GetOne(port)) << "Attempted to free port that was unallocated: " << port;
// Add the port to the quarantine list.
quarantined_ports_.push_back(QuarantinedPort{
.port = port, .available_time = async::Now(dispatcher_) + kPortQuarantineTime});
}
fidl::InterfaceRequestHandler<fuchsia::virtualization::HostVsockEndpoint>
HostVsockEndpoint::GetHandler() {
return bindings_.GetHandler(this);
}