| // 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); |
| } |