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