| // 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 |