blob: ef3d8a0b3fea4e6da7c0398cf811b34585425b11 [file] [log] [blame]
// Copyright 2020 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 "tun_pair.h"
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/syslog/global.h>
#include <fbl/auto_lock.h>
namespace network {
namespace tun {
TunPair::TunPair(async_dispatcher_t* dispatcher, fit::callback<void(TunPair*)> teardown,
DevicePairConfig config)
: teardown_callback_(std::move(teardown)), config_(std::move(config)) {}
zx::result<std::unique_ptr<TunPair>> TunPair::Create(const DeviceInterfaceDispatchers& dispatchers,
const ShimDispatchers& shim_dispatchers,
async_dispatcher_t* fidl_dispatcher,
fit::callback<void(TunPair*)> teardown,
DevicePairConfig&& config) {
fbl::AllocChecker ac;
std::unique_ptr<TunPair> tun(
new (&ac) TunPair(fidl_dispatcher, std::move(teardown), std::move(config)));
if (!ac.check()) {
return zx::error(ZX_ERR_NO_MEMORY);
}
zx::result left = DeviceAdapter::Create(dispatchers, shim_dispatchers, tun.get());
if (left.is_error()) {
FX_LOGF(ERROR, "tun", "TunDevice::Init device init left failed with %s", left.status_string());
return left.take_error();
}
tun->left_ = std::move(left.value());
zx::result right = DeviceAdapter::Create(dispatchers, shim_dispatchers, tun.get());
if (right.is_error()) {
FX_LOGF(ERROR, "tun", "TunDevice::Init device init right failed with %s",
right.status_string());
return right.take_error();
}
tun->right_ = std::move(right.value());
thrd_t thread;
if (zx_status_t status = tun->loop_.StartThread("tun-pair", &thread); status != ZX_OK) {
FX_LOGF(ERROR, "tun", "TunDevice::Init failed to start loop thread: %s",
zx_status_get_string(status));
return zx::error(status);
}
tun->loop_thread_ = thread;
return zx::ok(std::move(tun));
}
// Helper function to perform synchronous teardown of all adapters in a TunPair.
// Can be called with std::unique_ptr to MacAdapter or DeviceAdapter.
// Used in TunPair destructor.
template <typename T>
inline void AdapterTeardown(const T& adapter, sync_completion_t* completion) {
if (adapter) {
adapter->Teardown([completion] { sync_completion_signal(completion); });
} else {
sync_completion_signal(completion);
}
}
TunPair::~TunPair() {
if (loop_thread_.has_value()) {
// not allowed to destroy a tun pair on the loop thread, will cause deadlock
ZX_ASSERT(loop_thread_.value() != thrd_current());
}
// make sure that both devices and mac adapters are torn down:
sync_completion_t left_device_teardown;
sync_completion_t right_device_teardown;
AdapterTeardown(left_, &left_device_teardown);
AdapterTeardown(right_, &right_device_teardown);
sync_completion_wait(&left_device_teardown, ZX_TIME_INFINITE);
sync_completion_wait(&right_device_teardown, ZX_TIME_INFINITE);
loop_.Shutdown();
FX_VLOG(1, "tun", "TunPair destroyed");
}
void TunPair::Teardown() {
if (teardown_callback_) {
async::PostTask(loop_.dispatcher(), [this]() { teardown_callback_(this); });
}
}
void TunPair::Bind(fidl::ServerEnd<fuchsia_net_tun::DevicePair> req) {
binding_ =
fidl::BindServer(loop_.dispatcher(), std::move(req), this,
[](TunPair* impl, fidl::UnbindInfo,
fidl::ServerEnd<fuchsia_net_tun::DevicePair>) { impl->Teardown(); });
}
void TunPair::AddPort(AddPortRequestView request, AddPortCompleter::Sync& completer) {
zx_status_t status = [&request, this]() {
std::optional maybe_config = DevicePairPortConfig::Create(request->config);
if (!maybe_config.has_value()) {
return ZX_ERR_INVALID_ARGS;
}
DevicePairPortConfig& config = *maybe_config;
fbl::AutoLock lock(&power_lock_);
Ports& ports = ports_[config.port_id];
if (ports.left || ports.right) {
return ZX_ERR_ALREADY_EXISTS;
}
zx::result left = Port::Create(this, true, config, std::move(config.mac_left));
if (left.is_error()) {
return left.status_value();
}
zx::result right = Port::Create(this, false, config, std::move(config.mac_right));
if (right.is_error()) {
return right.status_value();
}
zx_status_t status = left_->AddPort(left->adapter());
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "failed to add left port: %s", zx_status_get_string(status));
return status;
}
status = right_->AddPort(right->adapter());
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "failed to add right port: %s", zx_status_get_string(status));
return status;
}
ports.left = std::move(*left);
ports.right = std::move(*right);
return ZX_OK;
}();
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void TunPair::RemovePort(RemovePortRequestView request, RemovePortCompleter::Sync& completer) {
zx_status_t status = [port_id = request->id, this]() {
fbl::AutoLock lock(&power_lock_);
if (port_id >= ports_.size() || !ports_[port_id].left || !ports_[port_id].right) {
return ZX_ERR_NOT_FOUND;
}
left_->RemovePort(port_id);
right_->RemovePort(port_id);
return ZX_OK;
}();
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void TunPair::GetLeft(GetLeftRequestView request, GetLeftCompleter::Sync& _completer) {
zx_status_t status = left_->Bind(std::move(request->device));
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "bind to left device failed: %s", zx_status_get_string(status));
}
}
void TunPair::GetRight(GetRightRequestView request, GetRightCompleter::Sync& _completer) {
zx_status_t status = right_->Bind(std::move(request->device));
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "bind to right device failed: %s", zx_status_get_string(status));
}
}
void TunPair::GetLeftPort(GetLeftPortRequestView request, GetLeftPortCompleter::Sync& _completer) {
zx_status_t status = left_->BindPort(request->id, std::move(request->port));
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "bind to left port %d failed: %s", request->id,
zx_status_get_string(status));
}
}
void TunPair::GetRightPort(GetRightPortRequestView request,
GetRightPortCompleter::Sync& _completer) {
zx_status_t status = right_->BindPort(request->id, std::move(request->port));
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "bind to right port %d failed: %s", request->id,
zx_status_get_string(status));
}
}
void TunPair::OnTxAvail(DeviceAdapter* device) {
DeviceAdapter* target;
bool fallible;
// Device is the transmitter, fallible is read for the end that matches device.
if (device == left_.get()) {
target = right_.get();
fallible = config_.fallible_transmit_left;
} else {
target = left_.get();
fallible = config_.fallible_transmit_right;
}
device->CopyTo(target, fallible);
}
void TunPair::OnRxAvail(DeviceAdapter* device) {
DeviceAdapter* source;
bool fallible;
// Device is the receiver, fallible is read for the source end.
if (device == left_.get()) {
source = right_.get();
fallible = config_.fallible_transmit_right;
} else {
source = left_.get();
fallible = config_.fallible_transmit_left;
}
source->CopyTo(device, fallible);
}
zx::result<std::unique_ptr<TunPair::Port>> TunPair::Port::Create(
TunPair* parent, bool left, const BasePortConfig& config,
std::optional<fuchsia_net::wire::MacAddress> mac) {
std::unique_ptr<Port> port(new Port(parent, left));
std::unique_ptr<MacAdapter> mac_adapter;
if (mac.has_value()) {
zx::result status = MacAdapter::Create(port.get(), std::move(*mac), true);
if (status.is_error()) {
return status.take_error();
}
mac_adapter = std::move(*status);
}
port->adapter_ = std::make_unique<PortAdapter>(port.get(), config, std::move(mac_adapter));
return zx::ok(std::move(port));
}
void TunPair::Port::OnHasSessionsChanged(PortAdapter& port) {
TunPair& parent = *parent_;
fbl::AutoLock lock(&parent.power_lock_);
uint8_t port_id = port.id();
Ports& ports = parent.ports_[port_id];
// Race between teardown of left or right ports might be observed. If any of the ends is not set
// assume port teardown is in process and skip this update.
if (!ports.left || !ports.right) {
return;
}
PortAdapter& left_adapter = ports.left->adapter();
PortAdapter& right_adapter = ports.right->adapter();
bool online = left_adapter.has_sessions() && right_adapter.has_sessions();
left_adapter.SetOnline(online);
right_adapter.SetOnline(online);
}
void TunPair::Port::OnMacStateChanged(MacAdapter* adapter) {
// Do nothing, TunPair doesn't care about mac state.
}
void TunPair::Port::OnPortStatusChanged(PortAdapter& port, const port_status_t& new_status) {
TunPair& parent = *parent_;
DeviceAdapter& device = [l = left_, &parent]() -> DeviceAdapter& {
if (l) {
return *parent.left_;
} else {
return *parent.right_;
}
}();
device.OnPortStatusChanged(port.id(), new_status);
}
void TunPair::Port::OnPortDestroyed(PortAdapter& port) {
TunPair& parent = *parent_;
fbl::AutoLock lock(&parent.power_lock_);
Ports& ports = parent.ports_[port.id()];
if (left_) {
ports.left = nullptr;
} else {
ports.right = nullptr;
}
}
} // namespace tun
} // namespace network