blob: 20c76dcda708b5ad60e471a76e6da3eaa71c0b2b [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_device.h"
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/syslog/global.h>
#include <zircon/status.h>
#include <fbl/auto_lock.h>
#include "util.h"
namespace network {
namespace tun {
namespace {
bool MacListEquals(const std::vector<fuchsia::net::MacAddress>& l,
const std::vector<fuchsia::net::MacAddress>& r) {
auto li = l.begin();
auto ri = r.begin();
for (; li != l.end() && ri != r.end(); li++, ri++) {
if (memcmp(li->octets.data(), ri->octets.data(), MAC_SIZE) != 0) {
return false;
}
}
return li == l.end() && ri == r.end();
}
} // namespace
TunDevice::TunDevice(fit::callback<void(TunDevice*)> teardown,
fuchsia::net::tun::DeviceConfig config)
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
teardown_callback_(std::move(teardown)),
config_(std::move(config)),
binding_(this) {
binding_.set_error_handler([this](zx_status_t) { Teardown(); });
}
zx_status_t TunDevice::Create(fit::callback<void(TunDevice*)> teardown,
fuchsia::net::tun::DeviceConfig config,
std::unique_ptr<TunDevice>* out) {
if (!TryConsolidateDeviceConfig(&config)) {
return ZX_ERR_INVALID_ARGS;
}
std::unique_ptr<TunDevice> tun(new TunDevice(std::move(teardown), std::move(config)));
zx_status_t status = zx::eventpair::create(0, &tun->signals_peer_, &tun->signals_self_);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "TunDevice::Init failed to create eventpair %s",
zx_status_get_string(status));
return status;
}
status = DeviceAdapter::Create(tun->loop_.dispatcher(), tun.get(), tun->config_.online(),
&tun->device_);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "TunDevice::Init device init failed with %s",
zx_status_get_string(status));
return status;
}
if (tun->config_.has_mac()) {
status = MacAdapter::Create(tun.get(), tun->config_.mac(), false, &tun->mac_);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "TunDevice::Init mac init failed with %s",
zx_status_get_string(status));
return status;
}
}
thrd_t thread;
status = tun->loop_.StartThread("tun-device", &thread);
if (status != ZX_OK) {
return status;
}
tun->loop_thread_ = thread;
*out = std::move(tun);
return ZX_OK;
}
TunDevice::~TunDevice() {
if (loop_thread_.has_value()) {
// not allowed to destroy a tun device on the loop thread, will cause deadlock
ZX_ASSERT(loop_thread_.value() != thrd_current());
}
// make sure that device is torn down:
if (device_) {
device_->TeardownSync();
}
if (mac_) {
mac_->TeardownSync();
}
loop_.Shutdown();
FX_VLOG(1, "tun", "TunDevice destroyed");
}
void TunDevice::Bind(fidl::InterfaceRequest<fuchsia::net::tun::Device> req) {
binding_.Bind(std::move(req), loop_.dispatcher());
}
void TunDevice::Teardown() {
if (teardown_callback_) {
teardown_callback_(this);
}
}
void TunDevice::RunWriteFrame() {
while (!pending_write_frame_.empty()) {
size_t avail = 0;
auto& pending = pending_write_frame_.front();
auto result = device_->WriteRxFrame(
pending.frame.frame_type(), pending.frame.data(),
pending.frame.has_meta() ? pending.frame.mutable_meta() : nullptr, &avail);
if (result == ZX_ERR_SHOULD_WAIT && IsBlocking()) {
return;
}
if (result != ZX_OK) {
pending.callback(fit::error(result));
} else {
if (avail == 0) {
// Clear the writable signal if no more buffers are available afterwards.
signals_self_.signal_peer(static_cast<uint32_t>(fuchsia::net::tun::Signals::WRITABLE), 0);
}
pending.callback(fit::ok());
}
pending_write_frame_.pop();
}
}
void TunDevice::RunReadFrame() {
while (!pending_read_frame_.empty()) {
auto success = device_->TryGetTxBuffer([this](Buffer* buff, size_t avail) {
std::vector<uint8_t> data;
zx_status_t status = buff->Read(&data);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "Failed to read from tx buffer: %s", zx_status_get_string(status));
} else if (data.empty()) {
FX_LOG(WARNING, "tun", "Ignoring empty tx buffer");
} else {
auto& callback = pending_read_frame_.front();
fuchsia::net::tun::Device_ReadFrame_Response rsp;
rsp.frame.set_data(std::move(data));
rsp.frame.set_frame_type(buff->frame_type());
auto meta = buff->TakeMetadata();
if (meta) {
rsp.frame.set_meta(std::move(*meta));
}
callback(fuchsia::net::tun::Device_ReadFrame_Result::WithResponse(std::move(rsp)));
pending_read_frame_.pop();
if (avail == 0) {
// clear SIGNAL_READABLE if we don't have any more tx buffers.
signals_self_.signal_peer(static_cast<uint32_t>(fuchsia::net::tun::Signals::READABLE), 0);
}
}
});
if (!success) {
if (IsBlocking()) {
return;
} else {
auto& callback = pending_read_frame_.front();
callback(fuchsia::net::tun::Device_ReadFrame_Result::WithErr(ZX_ERR_SHOULD_WAIT));
pending_read_frame_.pop();
}
}
}
}
void TunDevice::RunStateChange() {
if (!pending_watch_state_) {
return;
}
fuchsia::net::tun::InternalState state;
state.set_has_session(device_->HasSession());
{
if (mac_) {
fuchsia::net::tun::MacState mac_state;
mac_->CloneMacState(&mac_state);
state.set_mac(std::move(mac_state));
}
}
if (last_state_.has_value()) {
auto& last = last_state_.value();
// only continue if any changes actually occurred compared to the last observed state
if (last.has_session() == state.has_session() &&
(!last.has_mac() ||
(last.mac().mode() == state.mac().mode() &&
MacListEquals(last.mac().multicast_filters(), state.mac().multicast_filters())))) {
return;
}
}
fuchsia::net::tun::InternalState clone;
state.Clone(&clone);
// store the last informed state through WatchState
last_state_ = std::move(clone);
pending_watch_state_(std::move(state));
pending_watch_state_ = nullptr;
}
void TunDevice::WriteFrame(fuchsia::net::tun::Frame frame,
fuchsia::net::tun::Device::WriteFrameCallback callback) {
if (pending_write_frame_.size() >= kMaxPendingOps) {
callback(fit::error(ZX_ERR_NO_RESOURCES));
return;
}
if (!(frame.has_frame_type() && frame.has_data() && !frame.data().empty())) {
callback(fit::error(ZX_ERR_INVALID_ARGS));
return;
}
pending_write_frame_.emplace(std::move(frame), std::move(callback));
RunWriteFrame();
}
void TunDevice::ReadFrame(fuchsia::net::tun::Device::ReadFrameCallback callback) {
if (pending_read_frame_.size() >= kMaxPendingOps) {
callback(fit::error(ZX_ERR_NO_RESOURCES));
return;
}
pending_read_frame_.push(std::move(callback));
RunReadFrame();
}
void TunDevice::GetSignals(fuchsia::net::tun::Device::GetSignalsCallback callback) {
zx::eventpair dup;
signals_peer_.duplicate(ZX_RIGHTS_BASIC, &dup);
callback(std::move(dup));
}
void TunDevice::GetState(fuchsia::net::tun::Device::GetStateCallback callback) {
fuchsia::net::tun::InternalState state;
state.set_has_session(device_->HasSession());
if (mac_) {
fuchsia::net::tun::MacState mac_state;
mac_->CloneMacState(&mac_state);
state.set_mac(std::move(mac_state));
}
callback(std::move(state));
}
void TunDevice::WatchState(fuchsia::net::tun::Device::WatchStateCallback callback) {
if (pending_watch_state_) {
// this is a programming error, we enforce that clients don't do this by closing their channel.
binding_.Close(ZX_ERR_INTERNAL);
Teardown();
return;
}
pending_watch_state_ = std::move(callback);
RunStateChange();
}
void TunDevice::SetOnline(bool online, fuchsia::net::tun::Device::SetOnlineCallback callback) {
device_->SetOnline(online);
if (!online) {
// if we just went offline, we need to complete all pending writes.
RunWriteFrame();
}
callback();
}
void TunDevice::ConnectProtocols(fuchsia::net::tun::Protocols protos) {
if (device_ && protos.has_network_device()) {
auto status = device_->Bind(protos.mutable_network_device()->TakeChannel());
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "Failed to bind to network device: %s", zx_status_get_string(status));
}
}
if (mac_ && protos.has_mac_addressing()) {
auto status = mac_->Bind(loop_.dispatcher(), protos.mutable_mac_addressing()->TakeChannel());
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "Failed to bind to mac addressing: %s", zx_status_get_string(status));
}
}
}
void TunDevice::OnHasSessionsChanged(DeviceAdapter* device) {
async::PostTask(loop_.dispatcher(), [this]() { RunStateChange(); });
}
void TunDevice::OnTxAvail(DeviceAdapter* device) {
signals_self_.signal_peer(0, static_cast<uint32_t>(fuchsia::net::tun::Signals::READABLE));
async::PostTask(loop_.dispatcher(), [this]() { RunReadFrame(); });
}
void TunDevice::OnRxAvail(DeviceAdapter* device) {
signals_self_.signal_peer(0, static_cast<uint32_t>(fuchsia::net::tun::Signals::WRITABLE));
async::PostTask(loop_.dispatcher(), [this]() { RunWriteFrame(); });
}
void TunDevice::OnMacStateChanged(MacAdapter* adapter) {
async::PostTask(loop_.dispatcher(), [this]() {
RunWriteFrame();
RunStateChange();
});
}
} // namespace tun
} // namespace network