blob: c79a60ecb4baa524ae7f27f9d6524338b6a216f5 [file] [log] [blame]
// Copyright 2019 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/developer/vsock-sshd-host/service.h"
#include <fidl/fuchsia.component/cpp/fidl.h>
#include <fidl/fuchsia.process/cpp/fidl.h>
#include <fidl/fuchsia.vsock/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/fd.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/socket.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/processargs.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <vector>
#include "src/lib/fxl/strings/string_printf.h"
namespace sshd_host {
Service::Service(async_dispatcher_t* dispatcher, uint16_t port) : dispatcher_(dispatcher) {
auto connector = component::Connect<fuchsia_vsock::Connector>();
if (connector.is_error()) {
FX_LOGS(FATAL) << "Failed to connect to vsock connector" << connector.status_string();
}
FX_LOG_KV(INFO, "listen() for inbound SSH connections", FX_KV("port", int{port}));
auto [acceptor_client, acceptor_server] = fidl::Endpoints<fuchsia_vsock::Acceptor>::Create();
auto listen_result = fidl::Call(*connector)
->Listen({{
.local_port = port,
.acceptor = std::move(acceptor_client),
}});
if (listen_result.is_error()) {
FX_LOGS(FATAL) << "Failed to listen: " << listen_result.error_value();
}
binding_.emplace(dispatcher_, std::move(acceptor_server), this, fidl::kIgnoreBindingClosure);
}
Service::~Service() = default;
void Service::Accept(AcceptRequest& request, AcceptCompleter::Sync& completer) {
FX_LOG_KV(INFO, "Accepted connection", FX_KV("local_port", request.addr().local_port()),
FX_KV("remote_cid", request.addr().remote_cid()),
FX_KV("remote_port", request.addr().remote_port()));
zx::socket sock1, sock2;
ZX_ASSERT(zx::socket::create(0, &sock1, &sock2) == ZX_OK);
Launch(std::move(sock1));
auto [client_end, server_end] = fidl::Endpoints<fuchsia_vsock::Connection>::Create();
client_ends_.push_back(std::move(client_end));
completer.Reply({{std::make_unique<fuchsia_vsock::ConnectionTransport>(std::move(sock2),
std::move(server_end))}});
}
void Service::Launch(zx::socket socket) {
uint64_t child_num = next_child_num_++;
std::string child_name = fxl::StringPrintf("sshd-%lu", child_num);
auto realm_client_end = component::Connect<fuchsia_component::Realm>();
if (realm_client_end.is_error()) {
FX_PLOGS(ERROR, realm_client_end.status_value()) << "Failed to connect to realm service";
return;
}
fidl::SyncClient<fuchsia_component::Realm> realm{std::move(*realm_client_end)};
auto controller_endpoints = fidl::CreateEndpoints<fuchsia_component::Controller>();
if (controller_endpoints.is_error()) {
FX_PLOGS(ERROR, controller_endpoints.status_value())
<< "Failed to connect to create controller endpoints";
return;
}
fidl::SyncClient<fuchsia_component::Controller> controller{
std::move(controller_endpoints->client)};
{
fuchsia_component_decl::CollectionRef collection{{
.name = std::string(kShellCollection),
}};
fuchsia_component_decl::Child decl{{.name = child_name,
.url = "#meta/vsock-sshd.cm",
.startup = fuchsia_component_decl::StartupMode::kLazy}};
fuchsia_component::CreateChildArgs args{
{.controller = std::move(controller_endpoints->server)}};
auto result = realm->CreateChild(
{{.collection = collection, .decl = std::move(decl), .args = std::move(args)}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to create sshd child: " << result.error_value().FormatDescription();
return;
}
}
auto execution_controller_endpoints =
fidl::CreateEndpoints<fuchsia_component::ExecutionController>();
if (execution_controller_endpoints.is_error()) {
FX_LOGS(ERROR) << "Failed to create execution controller endpoints: "
<< execution_controller_endpoints.status_string();
return;
}
controllers_.emplace(std::piecewise_construct, std::forward_as_tuple(child_num),
std::forward_as_tuple(this, child_num, std::move(child_name),
std::move(execution_controller_endpoints->client),
dispatcher_, std::move(realm)));
auto remove_controller_on_error =
fit::defer([this, child_num]() { controllers_.erase(child_num); });
// Pass the socket as stdin and stdout handles to the sshd component.
std::vector<fuchsia_process::HandleInfo> numbered_handles;
zx::socket socket_dup;
if (zx_status_t status = socket.duplicate(ZX_RIGHT_SAME_RIGHTS, &socket_dup); status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to clone connection socket " << socket.get();
return;
}
numbered_handles.push_back(fuchsia_process::HandleInfo{
{.handle = std::move(socket_dup), .id = PA_HND(PA_FD, STDIN_FILENO)}});
numbered_handles.push_back(fuchsia_process::HandleInfo{
{.handle = std::move(socket), .id = PA_HND(PA_FD, STDOUT_FILENO)}});
auto result = controller->Start(
{{.args = {{
.numbered_handles = std::move(numbered_handles),
.namespace_entries = {},
}},
.execution_controller = std::move(execution_controller_endpoints->server)}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to start sshd child: " << result.error_value().FormatDescription();
return;
}
remove_controller_on_error.cancel();
}
void Service::OnStop(zx_status_t status, Controller* ptr) {
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "sshd component stopped with error";
}
// Destroy the component.
auto result = ptr->realm_->DestroyChild({{.child = {{
.name = ptr->child_name_,
.collection = std::string(kShellCollection),
}}}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to destroy sshd child: " << result.error_value().FormatDescription();
}
// Remove the controller.
controllers_.erase(ptr->child_num_);
}
} // namespace sshd_host