|  | // Copyright 2019 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 "fake_netstack.h" | 
|  |  | 
|  | #include <lib/async/default.h> | 
|  | #include <lib/fit/defer.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  | #include <netinet/icmp6.h> | 
|  | #include <netinet/if_ether.h> | 
|  | #include <netinet/ip.h> | 
|  | #include <netinet/ip6.h> | 
|  | #include <zircon/device/ethernet.h> | 
|  |  | 
|  | static constexpr size_t kMtu = 1500; | 
|  | static constexpr size_t kVmoSize = kMtu * 2; | 
|  |  | 
|  | static constexpr uint8_t kHostMacAddress[ETH_ALEN] = {0x02, 0x1a, 0x11, 0x00, 0x00, 0x00}; | 
|  |  | 
|  | static constexpr uint8_t kHostIpv4Address[4] = {192, 168, 0, 1}; | 
|  | static constexpr uint8_t kGuestIpv4Address[4] = {192, 168, 0, 10}; | 
|  |  | 
|  | static constexpr uint16_t kProtocolIpv4 = 0x0800; | 
|  | static constexpr uint8_t kPacketTypeUdp = 17; | 
|  | static constexpr uint16_t kTestPort = 4242; | 
|  |  | 
|  | zx_status_t Device::Create(fuchsia::hardware::ethernet::DeviceSyncPtr eth_device, | 
|  | std::unique_ptr<Device>* out) { | 
|  | std::unique_ptr<fuchsia::hardware::ethernet::Fifos> fifos; | 
|  | zx_status_t status; | 
|  | eth_device->GetFifos(&status, &fifos); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to get fifos: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | zx::vmo vmo; | 
|  | status = zx::vmo::create(kVmoSize, 0, &vmo); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to create vmo: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | zx::vmo vmo_dup; | 
|  | status = vmo.duplicate(ZX_RIGHTS_IO | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER, &vmo_dup); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to duplicate vmo: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | eth_device->SetIOBuffer(std::move(vmo_dup), &status); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to set IO buffer: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | uintptr_t io_addr; | 
|  | status = zx::vmar::root_self()->map( | 
|  | 0, vmo, 0, kVmoSize, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE, | 
|  | &io_addr); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to map vmo: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | eth_fifo_entry_t entry; | 
|  | entry.offset = 0; | 
|  | entry.length = kMtu; | 
|  | entry.flags = 0; | 
|  | entry.cookie = 0; | 
|  | status = fifos->rx.write(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to write to rx fifo: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | eth_device->Start(&status); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to start ethernet device: " << status; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | *out = std::unique_ptr<Device>(new Device(std::move(eth_device), std::move(fifos->rx), | 
|  | std::move(fifos->tx), std::move(vmo), io_addr)); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t Device::Start(async_dispatcher_t* dispatcher) { | 
|  | zx_status_t status = rx_wait_.Begin(dispatcher); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  | return tx_wait_.Begin(dispatcher); | 
|  | } | 
|  |  | 
|  | void Device::OnReceive(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, | 
|  | const zx_packet_signal_t* signal) { | 
|  | if (status == ZX_ERR_CANCELED) { | 
|  | return; | 
|  | } else if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Device receive waiter failed " << status; | 
|  | return; | 
|  | } | 
|  |  | 
|  | { | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  |  | 
|  | if (signal->observed & ZX_SOCKET_PEER_CLOSED) { | 
|  | while (!rx_completers_.empty()) { | 
|  | rx_completers_.back().complete_error(ZX_ERR_PEER_CLOSED); | 
|  | rx_completers_.pop_back(); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | while (!rx_completers_.empty()) { | 
|  | eth_fifo_entry_t entry; | 
|  | zx_status_t status = rx_.read(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); | 
|  | if (status == ZX_ERR_SHOULD_WAIT) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (status != ZX_OK) { | 
|  | rx_completers_.back().complete_error(status); | 
|  | rx_completers_.pop_back(); | 
|  | break; | 
|  | ; | 
|  | } | 
|  | rx_completers_.back().complete_ok(std::move(entry)); | 
|  | rx_completers_.pop_back(); | 
|  | } | 
|  | } | 
|  |  | 
|  | status = wait->Begin(dispatcher); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to wait for device rx fifo " << status; | 
|  | } | 
|  | } | 
|  |  | 
|  | void Device::OnTransmit(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, | 
|  | const zx_packet_signal_t* signal) { | 
|  | if (status == ZX_ERR_CANCELED) { | 
|  | return; | 
|  | } else if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Device receive waiter failed " << status; | 
|  | return; | 
|  | } | 
|  |  | 
|  | { | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  |  | 
|  | if (signal->observed & ZX_SOCKET_PEER_CLOSED) { | 
|  | while (!tx_completers_.empty()) { | 
|  | tx_completers_.back().complete_error(ZX_ERR_PEER_CLOSED); | 
|  | tx_completers_.pop_back(); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | while (!tx_completers_.empty()) { | 
|  | eth_fifo_entry_t entry; | 
|  | zx_status_t status = tx_.read(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); | 
|  | if (status == ZX_ERR_SHOULD_WAIT) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (status != ZX_OK) { | 
|  | tx_completers_.back().complete_error(status); | 
|  | tx_completers_.pop_back(); | 
|  | break; | 
|  | ; | 
|  | } | 
|  | tx_completers_.back().complete_ok(std::move(entry)); | 
|  | tx_completers_.pop_back(); | 
|  | } | 
|  | } | 
|  |  | 
|  | status = wait->Begin(dispatcher); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to wait for device tx fifo " << status; | 
|  | } | 
|  | } | 
|  |  | 
|  | fit::promise<eth_fifo_entry_t, zx_status_t> Device::GetRxEntry() { | 
|  | fit::bridge<eth_fifo_entry_t, zx_status_t> bridge; | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  | rx_completers_.push_back(std::move(bridge.completer)); | 
|  | return bridge.consumer.promise(); | 
|  | } | 
|  |  | 
|  | fit::promise<eth_fifo_entry_t, zx_status_t> Device::GetTxEntry() { | 
|  | fit::bridge<eth_fifo_entry_t, zx_status_t> bridge; | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  | tx_completers_.push_back(std::move(bridge.completer)); | 
|  | return bridge.consumer.promise(); | 
|  | } | 
|  |  | 
|  | fit::promise<std::vector<uint8_t>, zx_status_t> Device::ReadPacket() { | 
|  | return GetRxEntry().and_then( | 
|  | [this](const eth_fifo_entry_t& entry) -> fit::result<std::vector<uint8_t>, zx_status_t> { | 
|  | if (entry.flags != ETH_FIFO_RX_OK) { | 
|  | return fit::error(ZX_ERR_IO); | 
|  | } | 
|  | if (entry.length > kMtu) { | 
|  | return fit::error(ZX_ERR_INTERNAL); | 
|  | } | 
|  | std::vector<uint8_t> packet(entry.length); | 
|  | memcpy(packet.data(), reinterpret_cast<void*>(io_addr_ + entry.offset), packet.size()); | 
|  |  | 
|  | memset(reinterpret_cast<void*>(io_addr_), 0, kMtu); | 
|  | eth_fifo_entry_t new_entry; | 
|  | new_entry.offset = 0; | 
|  | new_entry.length = kMtu; | 
|  | new_entry.flags = 0; | 
|  | new_entry.cookie = 0; | 
|  | zx_status_t status = rx_.write(sizeof(eth_fifo_entry_t), &new_entry, 1, nullptr); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to write to rx fifo: " << status; | 
|  | return fit::error(status); | 
|  | } | 
|  |  | 
|  | return fit::ok(std::move(packet)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | fit::promise<void, zx_status_t> Device::WritePacket(std::vector<uint8_t> packet) { | 
|  | eth_fifo_entry_t entry; | 
|  | entry.offset = kMtu; | 
|  | entry.length = packet.size(); | 
|  | entry.flags = 0; | 
|  | entry.cookie = 0; | 
|  |  | 
|  | memcpy(reinterpret_cast<void*>(io_addr_ + entry.offset), packet.data(), packet.size()); | 
|  |  | 
|  | size_t count; | 
|  | zx_status_t status = tx_.write(sizeof(eth_fifo_entry_t), &entry, 1, &count); | 
|  | if (status != ZX_OK) { | 
|  | return fit::make_error_promise(status); | 
|  | } | 
|  | if (count != 1) { | 
|  | return fit::make_error_promise(ZX_ERR_INTERNAL); | 
|  | } | 
|  |  | 
|  | return GetTxEntry().and_then([](const eth_fifo_entry_t& entry) -> fit::result<void, zx_status_t> { | 
|  | if (entry.flags != ETH_FIFO_TX_OK) { | 
|  | return fit::error(ZX_ERR_IO); | 
|  | } | 
|  | return fit::ok(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void FakeNetstack::AddEthernetDevice( | 
|  | std::string topological_path, fuchsia::netstack::InterfaceConfig interfaceConfig, | 
|  | fidl::InterfaceHandle<::fuchsia::hardware::ethernet::Device> eth_device, | 
|  | AddEthernetDeviceCallback callback) { | 
|  | auto deferred = fit::defer([this, callback = std::move(callback)]() { | 
|  | callback(nic_counter_); | 
|  | nic_counter_++; | 
|  | }); | 
|  |  | 
|  | auto device_sync_ptr = eth_device.BindSync(); | 
|  |  | 
|  | fuchsia::hardware::ethernet::Info device_info; | 
|  | zx_status_t status = device_sync_ptr->GetInfo(&device_info); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to get device info: " << status; | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<Device> device; | 
|  | status = Device::Create(std::move(device_sync_ptr), &device); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to create device " << status; | 
|  | return; | 
|  | } | 
|  |  | 
|  | status = device->Start(loop_.dispatcher()); | 
|  | if (status != ZX_OK) { | 
|  | FX_LOGS(ERROR) << "Failed to start device " << status; | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  | auto [itr, success] = devices_.insert(std::make_pair(device_info.mac, std::move(device))); | 
|  | if (!success) { | 
|  | FX_LOGS(ERROR) << "Device already exists"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto completers_itr = completers_.find(device_info.mac); | 
|  | if (completers_itr == completers_.end()) { | 
|  | return; | 
|  | } | 
|  | while (!completers_itr->second.empty()) { | 
|  | completers_itr->second.back().complete_ok(itr->second.get()); | 
|  | completers_itr->second.pop_back(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FakeNetstack::SetInterfaceAddress(uint32_t nicid, fuchsia::net::IpAddress addr, | 
|  | uint8_t prefixLen, SetInterfaceAddressCallback callback) { | 
|  | fuchsia::netstack::NetErr err; | 
|  |  | 
|  | if (nicid >= nic_counter_) { | 
|  | err.status = fuchsia::netstack::Status::UNKNOWN_INTERFACE; | 
|  | err.message = "No such interface."; | 
|  | } else { | 
|  | err.status = fuchsia::netstack::Status::OK; | 
|  | err.message = ""; | 
|  | } | 
|  |  | 
|  | callback(std::move(err)); | 
|  | } | 
|  |  | 
|  | static uint16_t checksum(const void* _data, size_t len, uint16_t _sum) { | 
|  | uint32_t sum = _sum; | 
|  | auto data = static_cast<const uint16_t*>(_data); | 
|  | for (; len > 1; len -= 2) { | 
|  | sum += *data++; | 
|  | } | 
|  | if (len) { | 
|  | sum += (*data & UINT8_MAX); | 
|  | } | 
|  | while (sum > UINT16_MAX) { | 
|  | sum = (sum & UINT16_MAX) + (sum >> 16); | 
|  | } | 
|  | return ~sum; | 
|  | } | 
|  |  | 
|  | static size_t make_ip_header(const uint8_t guest_mac[ETH_ALEN], uint8_t packet_type, size_t length, | 
|  | uint8_t* data) { | 
|  | // First construct the ethernet header. | 
|  | ethhdr* eth = reinterpret_cast<ethhdr*>(data); | 
|  | memcpy(eth->h_dest, guest_mac, ETH_ALEN); | 
|  | memcpy(eth->h_source, kHostMacAddress, ETH_ALEN); | 
|  | eth->h_proto = htons(kProtocolIpv4); | 
|  |  | 
|  | // Now construct the IPv4 header. | 
|  | auto ip = reinterpret_cast<iphdr*>(data + sizeof(ethhdr)); | 
|  | ip->version = 4; | 
|  | ip->ihl = sizeof(iphdr) >> 2;  // Header length in 32-bit words. | 
|  | ip->tos = 0; | 
|  | ip->tot_len = htons(sizeof(iphdr) + length); | 
|  | ip->id = 0; | 
|  | ip->frag_off = 0; | 
|  | ip->ttl = UINT8_MAX; | 
|  | ip->protocol = packet_type; | 
|  | memcpy(&ip->saddr, kHostIpv4Address, sizeof(kHostIpv4Address)); | 
|  | memcpy(&ip->daddr, kGuestIpv4Address, sizeof(kGuestIpv4Address)); | 
|  | ip->check = 0; | 
|  | ip->check = checksum(ip, sizeof(iphdr), 0); | 
|  |  | 
|  | return sizeof(ethhdr) + sizeof(iphdr); | 
|  | } | 
|  |  | 
|  | fit::promise<void, zx_status_t> FakeNetstack::SendUdpPacket( | 
|  | const fuchsia::hardware::ethernet::MacAddress& mac_addr, std::vector<uint8_t> packet) { | 
|  | struct udp_hdr_t { | 
|  | uint16_t src_port; | 
|  | uint16_t dst_port; | 
|  | uint16_t length; | 
|  | uint16_t checksum; | 
|  | } __PACKED; | 
|  |  | 
|  | size_t packet_length = sizeof(udp_hdr_t) + packet.size(); | 
|  | size_t total_length = sizeof(ethhdr) + sizeof(iphdr) + packet_length; | 
|  | if (total_length > kMtu) { | 
|  | return fit::make_error_promise(ZX_ERR_BUFFER_TOO_SMALL); | 
|  | } | 
|  |  | 
|  | std::vector<uint8_t> udp_packet(total_length); | 
|  | size_t header_len = | 
|  | make_ip_header(mac_addr.octets.data(), kPacketTypeUdp, packet_length, udp_packet.data()); | 
|  |  | 
|  | uintptr_t off = header_len; | 
|  | auto udp = reinterpret_cast<udp_hdr_t*>(udp_packet.data() + off); | 
|  | udp->src_port = htons(kTestPort); | 
|  | udp->dst_port = htons(kTestPort); | 
|  | udp->length = htons(sizeof(udp_hdr_t) + packet.size()); | 
|  | // The checksum is optional for IPv4. | 
|  | udp->checksum = 0; | 
|  |  | 
|  | off += sizeof(udp_hdr_t); | 
|  | memcpy(udp_packet.data() + off, packet.data(), packet.size()); | 
|  |  | 
|  | return SendPacket(mac_addr, std::move(udp_packet)); | 
|  | } | 
|  |  | 
|  | fit::promise<Device*> FakeNetstack::GetDevice( | 
|  | const fuchsia::hardware::ethernet::MacAddress& mac_addr) { | 
|  | std::lock_guard<std::mutex> lock(mutex_); | 
|  |  | 
|  | // If the device is already connected the the netstack then just return a pointer to it. | 
|  | auto itr = devices_.find(mac_addr); | 
|  | if (itr != devices_.end()) { | 
|  | return fit::make_promise([device = &itr->second] { return fit::ok(device->get()); }); | 
|  | } | 
|  |  | 
|  | // Otherwise, add to the list of completers for this MAC address. The promise will complete when | 
|  | // the devices calls AddEthernetDevice. | 
|  | fit::bridge<Device*> bridge; | 
|  | auto completers_itr = completers_.find(mac_addr); | 
|  | if (completers_itr == completers_.end()) { | 
|  | std::vector<fit::completer<Device*>> vec; | 
|  | vec.push_back(std::move(bridge.completer)); | 
|  | completers_.insert(std::make_pair(mac_addr, std::move(vec))); | 
|  | } else { | 
|  | completers_itr->second.push_back(std::move(bridge.completer)); | 
|  | } | 
|  |  | 
|  | return bridge.consumer.promise(); | 
|  | } | 
|  |  | 
|  | fit::promise<void, zx_status_t> FakeNetstack::SendPacket( | 
|  | const fuchsia::hardware::ethernet::MacAddress& mac_addr, std::vector<uint8_t> packet) { | 
|  | if (packet.size() > kMtu) { | 
|  | return fit::make_error_promise(ZX_ERR_INVALID_ARGS); | 
|  | } | 
|  |  | 
|  | return GetDevice(mac_addr).then( | 
|  | [packet = std::move(packet)]( | 
|  | const fit::result<Device*>& result) mutable -> fit::promise<void, zx_status_t> { | 
|  | if (!result.is_ok()) { | 
|  | return fit::make_error_promise(ZX_ERR_INTERNAL); | 
|  | } | 
|  | Device* device = result.value(); | 
|  | return device->WritePacket(std::move(packet)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | fit::promise<std::vector<uint8_t>, zx_status_t> FakeNetstack::ReceivePacket( | 
|  | const fuchsia::hardware::ethernet::MacAddress& mac_addr) { | 
|  | return GetDevice(mac_addr).then( | 
|  | [](const fit::result<Device*>& result) -> fit::promise<std::vector<uint8_t>, zx_status_t> { | 
|  | if (!result.is_ok()) { | 
|  | return fit::make_result_promise<std::vector<uint8_t>, zx_status_t>( | 
|  | fit::error(ZX_ERR_INTERNAL)); | 
|  | } | 
|  | Device* device = result.value(); | 
|  | return device->ReadPacket(); | 
|  | }); | 
|  | } |