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