blob: 6c2b6adc46b02adf17dc6697b44cafbb3870f92f [file] [log] [blame]
// 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 "ethertap_client.h"
#include <fbl/unique_fd.h>
#include <fcntl.h>
#include <lib/async/cpp/wait.h>
#include <lib/async/default.h>
#include <lib/fit/function.h>
#include <lib/zx/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/device/ethertap.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <functional>
#include <iomanip>
#include <iostream>
#include <memory>
static const char kTapctl[] = "/dev/misc/tapctl";
typedef struct ethertap_data_in {
ethertap_socket_header_t header;
uint8_t data[];
} ethertap_data_in_t;
namespace netemul {
class EthertapClientImpl : public EthertapClient {
public:
explicit EthertapClientImpl(async_dispatcher_t* dispatcher, zx::socket sock,
EthertapConfig config)
: dispatcher_(dispatcher),
buf_(config.mtu + sizeof(ethertap_socket_header_t)),
config_(std::move(config)),
sock_(std::move(sock)) {
sock_data_wait_.set_object(sock_.get());
sock_data_wait_.set_trigger(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
WaitOnSocket();
}
void SetLinkUp(bool linkUp) override {
sock_.signal_peer(
0, linkUp ? ETHERTAP_SIGNAL_ONLINE : ETHERTAP_SIGNAL_OFFLINE);
}
zx_status_t Send(const void* data, size_t len, size_t* sent) override {
return sock_.write(0u, data, len, sent);
}
void SetPacketCallback(PacketCallback cb) override {
packet_callback_ = std::move(cb);
}
void SetPeerClosedCallback(PeerClosedCallback cb) override {
peer_closed_callback_ = std::move(cb);
}
static std::unique_ptr<EthertapClientImpl> Create(
async_dispatcher_t* dispatcher, const EthertapConfig& incfg) {
zx::socket sock;
fbl::unique_fd ctlfd(open(kTapctl, O_RDONLY));
if (!ctlfd.is_valid()) {
fprintf(stderr, "could not open %s: %s\n", kTapctl, strerror(errno));
return nullptr;
}
ethertap_ioctl_config_t config = {};
strlcpy(config.name, incfg.name.c_str(), ETHERTAP_MAX_NAME_LEN);
config.options = incfg.options;
config.mtu = incfg.mtu;
memcpy(config.mac, incfg.mac.d, 6);
ssize_t rc = ioctl_ethertap_config(ctlfd.get(), &config,
sock.reset_and_get_address());
if (rc < 0) {
auto status = static_cast<zx_status_t>(rc);
fprintf(stderr, "could not configure ethertap device: %s\n",
zx_status_get_string(status));
return nullptr;
}
return std::make_unique<EthertapClientImpl>(dispatcher, std::move(sock),
incfg);
}
void Close() override {
sock_data_wait_.Cancel();
sock_.reset();
}
const zx::socket& socket() override { return sock_; }
void OnSocketSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
fprintf(stderr, "Ethertap OnSocketSignal bad status %s\n",
zx_status_get_string(status));
return;
}
if (signal->observed & ZX_SOCKET_READABLE) {
size_t read;
status = sock_.read(0u, buf_.data(), buf_.size(), &read);
if (status != ZX_OK) {
fprintf(stderr, "Ethertap OnSocketSignal read failed %s\n",
zx_status_get_string(status));
return;
}
if (read < sizeof(ethertap_socket_header_t)) {
fprintf(
stderr,
"Ethertap socket read too short, expecting at least %ld header, "
"got %ld\n",
sizeof(ethertap_socket_header_t), read);
}
auto d = reinterpret_cast<ethertap_data_in_t*>(buf_.data());
read -= sizeof(ethertap_socket_header_t);
if (d->header.type == ETHERTAP_MSG_PACKET && packet_callback_) {
packet_callback_(d->data, read);
}
}
if (signal->observed & ZX_SOCKET_PEER_CLOSED) {
fprintf(stderr, "Ethertap OnSocketSignal peer closed\n");
if (peer_closed_callback_) {
peer_closed_callback_();
}
} else {
WaitOnSocket();
}
}
void WaitOnSocket() {
zx_status_t status = sock_data_wait_.Begin(dispatcher_);
if (status != ZX_OK) {
fprintf(stderr, "Can't wait on ethertap socket: %s\n",
zx_status_get_string(status));
}
}
async_dispatcher_t* dispatcher_;
std::vector<uint8_t> buf_;
EthertapConfig config_;
zx::socket sock_;
PacketCallback packet_callback_;
PeerClosedCallback peer_closed_callback_;
async::WaitMethod<EthertapClientImpl, &EthertapClientImpl::OnSocketSignal>
sock_data_wait_{this};
};
std::unique_ptr<EthertapClient> EthertapClient::Create(
const EthertapConfig& config, async_dispatcher_t* dispatcher) {
if (dispatcher == nullptr) {
dispatcher = async_get_default_dispatcher();
}
return EthertapClientImpl::Create(dispatcher, config);
}
} // namespace netemul