1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ethertap.h"
6
7#include <ddk/debug.h>
8#include <fbl/auto_lock.h>
9#include <fbl/type_support.h>
10#include <pretty/hexdump.h>
11#include <zircon/compiler.h>
12
13#include <stdio.h>
14#include <string.h>
15
16// This macro allows for per-device tracing rather than enabling tracing for the whole driver
17// TODO(tkilbourn): decide whether this is worth the effort
18#define ethertap_trace(args...) \
19  do { if (unlikely(options_ & ETHERTAP_OPT_TRACE)) zxlogf(INFO, "ethertap: " args); } while (0)
20
21namespace eth {
22
23TapCtl::TapCtl(zx_device_t* device) : ddk::Device<TapCtl, ddk::Ioctlable>(device) {}
24
25void TapCtl::DdkRelease() {
26    delete this;
27}
28
29zx_status_t TapCtl::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
30                             size_t out_len, size_t* out_actual) {
31    switch (op) {
32    case IOCTL_ETHERTAP_CONFIG: {
33        if (in_buf == NULL || in_len != sizeof(ethertap_ioctl_config_t) ||
34            out_buf == NULL || out_len != sizeof(zx_handle_t) || out_actual == NULL) {
35            return ZX_ERR_INVALID_ARGS;
36        }
37
38        ethertap_ioctl_config_t config;
39        memcpy(&config, in_buf, in_len);
40
41        if (config.mtu > ETHERTAP_MAX_MTU) {
42            return ZX_ERR_INVALID_ARGS;
43        }
44
45        zx::socket local, remote;
46        uint32_t sockopt = ZX_SOCKET_DATAGRAM |
47                           ((config.options & ETHERTAP_OPT_REPORT_PARAM) ? ZX_SOCKET_HAS_CONTROL : 0);
48        zx_status_t status = zx::socket::create(sockopt, &local, &remote);
49        if (status != ZX_OK) {
50            return status;
51        }
52
53        config.name[ETHERTAP_MAX_NAME_LEN] = '\0';
54
55        auto tap = fbl::unique_ptr<eth::TapDevice>(
56                new eth::TapDevice(zxdev(), &config, fbl::move(local)));
57
58        status = tap->DdkAdd(config.name);
59        if (status != ZX_OK) {
60            zxlogf(ERROR, "tapctl: could not add tap device: %d\n", status);
61        } else {
62            // devmgr owns the memory until release is called
63            __UNUSED auto ptr = tap.release();
64
65            zx_handle_t* out = reinterpret_cast<zx_handle_t*>(out_buf);
66            *out = remote.release();
67            *out_actual = sizeof(zx_handle_t);
68            zxlogf(INFO, "tapctl: created ethertap device '%s'\n", config.name);
69        }
70        return status;
71    }
72    default:
73        return ZX_ERR_NOT_SUPPORTED;
74    }
75}
76
77int tap_device_thread(void* arg) {
78    TapDevice* device = reinterpret_cast<TapDevice*>(arg);
79    return device->Thread();
80}
81
82#define TAP_SHUTDOWN ZX_USER_SIGNAL_7
83
84TapDevice::TapDevice(zx_device_t* device, const ethertap_ioctl_config* config, zx::socket data)
85  : ddk::Device<TapDevice, ddk::Unbindable>(device),
86    options_(config->options),
87    features_(config->features | ETHMAC_FEATURE_SYNTH),
88    mtu_(config->mtu),
89    data_(fbl::move(data)) {
90    ZX_DEBUG_ASSERT(data_.is_valid());
91    memcpy(mac_, config->mac, 6);
92
93    int ret = thrd_create_with_name(&thread_, tap_device_thread, reinterpret_cast<void*>(this),
94                                    "ethertap-thread");
95    ZX_DEBUG_ASSERT(ret == thrd_success);
96}
97
98void TapDevice::DdkRelease() {
99    ethertap_trace("DdkRelease\n");
100    // Only the thread can call DdkRemove(), which means the thread is exiting on its own. No need
101    // to join the thread.
102    delete this;
103}
104
105void TapDevice::DdkUnbind() {
106    ethertap_trace("DdkUnbind\n");
107    fbl::AutoLock lock(&lock_);
108    zx_status_t status = data_.signal(0, TAP_SHUTDOWN);
109    ZX_DEBUG_ASSERT(status == ZX_OK);
110    // When the thread exits after the channel is closed, it will call DdkRemove.
111}
112
113zx_status_t TapDevice::EthmacQuery(uint32_t options, ethmac_info_t* info) {
114    memset(info, 0, sizeof(*info));
115    info->features = features_;
116    info->mtu = mtu_;
117    memcpy(info->mac, mac_, 6);
118    return ZX_OK;
119}
120
121void TapDevice::EthmacStop() {
122    ethertap_trace("EthmacStop\n");
123    fbl::AutoLock lock(&lock_);
124    ethmac_proxy_.reset();
125}
126
127zx_status_t TapDevice::EthmacStart(fbl::unique_ptr<ddk::EthmacIfcProxy> proxy) {
128    ethertap_trace("EthmacStart\n");
129    fbl::AutoLock lock(&lock_);
130    if (ethmac_proxy_ != nullptr) {
131        return ZX_ERR_ALREADY_BOUND;
132    } else {
133        ethmac_proxy_.swap(proxy);
134        ethmac_proxy_->Status(online_ ? ETH_STATUS_ONLINE : 0u);
135    }
136    return ZX_OK;
137}
138
139zx_status_t TapDevice::EthmacQueueTx(uint32_t options, ethmac_netbuf_t* netbuf) {
140    fbl::AutoLock lock(&lock_);
141    if (dead_) {
142        return ZX_ERR_PEER_CLOSED;
143    }
144    uint8_t temp_buf[ETHERTAP_MAX_MTU + sizeof(ethertap_socket_header_t)];
145    auto header = reinterpret_cast<ethertap_socket_header*>(temp_buf);
146    uint8_t* data = temp_buf + sizeof(ethertap_socket_header_t);
147    size_t length = netbuf->len;
148    ZX_DEBUG_ASSERT(length <= mtu_);
149    memcpy(data, netbuf->data, length);
150    header->type = ETHERTAP_MSG_PACKET;
151
152    if (unlikely(options_ & ETHERTAP_OPT_TRACE_PACKETS)) {
153        ethertap_trace("sending %zu bytes\n", length);
154        hexdump8_ex(data, length, 0);
155    }
156    zx_status_t status = data_.write(0u, temp_buf, length + sizeof(ethertap_socket_header_t),
157                                     nullptr);
158    if (status != ZX_OK) {
159        zxlogf(ERROR, "ethertap: EthmacQueueTx error writing: %d\n", status);
160    }
161    // returning ZX_ERR_SHOULD_WAIT indicates that we will call complete_tx(), which we will not
162    return status == ZX_ERR_SHOULD_WAIT ? ZX_ERR_UNAVAILABLE : status;
163}
164
165zx_status_t TapDevice::EthmacSetParam(uint32_t param, int32_t value, void* data) {
166    fbl::AutoLock lock(&lock_);
167    if (!(options_ & ETHERTAP_OPT_REPORT_PARAM) || dead_) {
168        return ZX_ERR_NOT_SUPPORTED;
169    }
170
171    struct {
172        ethertap_socket_header_t header;
173        ethertap_setparam_report_t report;
174    } send_buf = {};
175
176    send_buf.header.type = ETHERTAP_MSG_PARAM_REPORT;
177    send_buf.report.param = param;
178    send_buf.report.value = value;
179    send_buf.report.data_length = 0;
180    switch (param) {
181    case ETHMAC_SETPARAM_MULTICAST_FILTER:
182        if (value == ETHMAC_MULTICAST_FILTER_OVERFLOW) {
183            break;
184        }
185        // Send the final byte of each address, sorted lowest-to-highest.
186        uint32_t i;
187        for (i = 0; i < static_cast<uint32_t>(value) && i < sizeof(send_buf.report.data); i++) {
188            send_buf.report.data[i] = static_cast<uint8_t*>(data)[i * ETH_MAC_SIZE + 5];
189        }
190        send_buf.report.data_length = i;
191        qsort(send_buf.report.data, send_buf.report.data_length, 1,
192              [](const void* ap, const void* bp) {
193                  int a = *static_cast<const uint8_t*>(ap);
194                  int b = *static_cast<const uint8_t*>(bp);
195                  return a < b ? -1 : (a > 1 ? 1 : 0);
196              });
197        break;
198    default:
199        break;
200    }
201    zx_status_t status = data_.write(0, &send_buf, sizeof(send_buf), nullptr);
202    if (status != ZX_OK) {
203        ethertap_trace("error writing SetParam info to socket: %d\n", status);
204    }
205    // A failure of data_.write is not a simulated failure of hardware under test, so log it but
206    // don't report failure on the SetParam attempt.
207    return ZX_OK;
208}
209
210zx_handle_t TapDevice::EthmacGetBti() {
211    return ZX_HANDLE_INVALID;
212}
213
214int TapDevice::Thread() {
215    ethertap_trace("starting main thread\n");
216    zx_signals_t pending;
217    fbl::unique_ptr<uint8_t[]> buf(new uint8_t[mtu_]);
218
219    zx_status_t status = ZX_OK;
220    const zx_signals_t wait = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED | ETHERTAP_SIGNAL_ONLINE
221        | ETHERTAP_SIGNAL_OFFLINE | TAP_SHUTDOWN;
222    while (true) {
223        status = data_.wait_one(wait, zx::time::infinite(), &pending);
224        if (status != ZX_OK) {
225            ethertap_trace("error waiting on data: %d\n", status);
226            break;
227        }
228
229        if (pending & (ETHERTAP_SIGNAL_OFFLINE | ETHERTAP_SIGNAL_ONLINE)) {
230            status = UpdateLinkStatus(pending);
231            if (status != ZX_OK) {
232                break;
233            }
234        }
235
236        if (pending & ZX_SOCKET_READABLE) {
237            status = Recv(buf.get(), mtu_);
238            if (status != ZX_OK) {
239                break;
240            }
241        }
242        if (pending & ZX_SOCKET_PEER_CLOSED) {
243            ethertap_trace("socket closed (peer)\n");
244            break;
245        }
246        if (pending & TAP_SHUTDOWN) {
247            ethertap_trace("socket closed (self)\n");
248            break;
249        }
250    }
251    {
252        fbl::AutoLock lock(&lock_);
253        dead_ = true;
254        zxlogf(INFO, "ethertap: device '%s' destroyed\n", name());
255        data_.reset();
256    }
257    DdkRemove();
258
259    return static_cast<int>(status);
260}
261
262static inline bool observed_online(zx_signals_t obs) {
263    return obs & ETHERTAP_SIGNAL_ONLINE;
264}
265
266static inline bool observed_offline(zx_signals_t obs) {
267    return obs & ETHERTAP_SIGNAL_OFFLINE;
268}
269
270zx_status_t TapDevice::UpdateLinkStatus(zx_signals_t observed) {
271    bool was_online = online_;
272    zx_signals_t clear = 0;
273
274    if (observed_online(observed) && observed_offline(observed)) {
275        zxlogf(ERROR, "ethertap: error asserting both online and offline\n");
276        return ZX_ERR_BAD_STATE;
277    }
278
279    if (observed_offline(observed)) {
280        ethertap_trace("offline asserted\n");
281        online_ = false;
282        clear |= ETHERTAP_SIGNAL_OFFLINE;
283    }
284    if (observed_online(observed)) {
285        ethertap_trace("online asserted\n");
286        online_ = true;
287        clear |= ETHERTAP_SIGNAL_ONLINE;
288    }
289
290    if (was_online != online_) {
291        fbl::AutoLock lock(&lock_);
292        if (ethmac_proxy_ != nullptr) {
293            ethmac_proxy_->Status(online_ ? ETH_STATUS_ONLINE : 0u);
294        }
295        ethertap_trace("device '%s' is now %s\n", name(), online_ ? "online" : "offline");
296    }
297    if (clear) {
298        zx_status_t status = data_.signal(clear, 0);
299        if (status != ZX_OK) {
300            zxlogf(ERROR, "ethertap: could not clear status signals: %d\n", status);
301            return status;
302        }
303    }
304    return ZX_OK;
305}
306
307zx_status_t TapDevice::Recv(uint8_t* buffer, uint32_t capacity) {
308    size_t actual = 0;
309    zx_status_t status = data_.read(0u, buffer, capacity, &actual);
310    if (status != ZX_OK) {
311        zxlogf(ERROR, "ethertap: error reading data: %d\n", status);
312        return status;
313    }
314
315    fbl::AutoLock lock(&lock_);
316    if (unlikely(options_ & ETHERTAP_OPT_TRACE_PACKETS)) {
317        ethertap_trace("received %zu bytes\n", actual);
318        hexdump8_ex(buffer, actual, 0);
319    }
320    if (ethmac_proxy_ != nullptr) {
321        ethmac_proxy_->Recv(buffer, actual, 0u);
322    }
323    return ZX_OK;
324}
325
326}  // namespace eth
327
328extern "C" zx_status_t tapctl_bind(void* ctx, zx_device_t* device, void** cookie) {
329    auto dev = fbl::unique_ptr<eth::TapCtl>(new eth::TapCtl(device));
330    zx_status_t status = dev->DdkAdd("tapctl");
331    if (status != ZX_OK) {
332        zxlogf(ERROR, "%s: could not add device: %d\n", __func__, status);
333    } else {
334        // devmgr owns the memory now
335        __UNUSED auto ptr = dev.release();
336    }
337    return status;
338}
339