| // 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 "mock_netstack.h" |
| |
| #include <lib/fit/defer.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/if_ether.h> |
| #include <netinet/ip.h> |
| #include <netinet/ip6.h> |
| #include <src/lib/fxl/logging.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 kGuestMacAddress[ETH_ALEN] = {0x02, 0x1a, 0x11, |
| 0x00, 0x01, 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; |
| |
| static constexpr uint32_t kMockNicId = 0; |
| |
| void MockNetstack::AddEthernetDevice( |
| std::string topological_path, |
| fuchsia::netstack::InterfaceConfig interfaceConfig, |
| fidl::InterfaceHandle<::fuchsia::hardware::ethernet::Device> device, |
| AddEthernetDeviceCallback callback) { |
| auto deferred = |
| fit::defer([callback = std::move(callback)]() { callback(kMockNicId); }); |
| eth_device_ = device.BindSync(); |
| |
| zx_status_t status; |
| std::unique_ptr<fuchsia::hardware::ethernet::Fifos> fifos; |
| eth_device_->GetFifos(&status, &fifos); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to get fifos: " << status; |
| return; |
| } |
| rx_ = std::move(fifos->rx); |
| tx_ = std::move(fifos->tx); |
| |
| status = zx::vmo::create(kVmoSize, 0, &vmo_); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create vmo: " << status; |
| return; |
| } |
| |
| zx::vmo vmo_dup; |
| status = |
| vmo_.duplicate(ZX_RIGHTS_IO | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER, &vmo_dup); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to duplicate vmo: " << status; |
| return; |
| } |
| |
| eth_device_->SetIOBuffer(std::move(vmo_dup), &status); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to set IO buffer: " << status; |
| return; |
| } |
| |
| 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) { |
| FXL_LOG(ERROR) << "Failed to map vmo: " << status; |
| return; |
| } |
| |
| eth_fifo_entry_t entry; |
| entry.offset = 0; |
| entry.length = kMtu; |
| entry.flags = 0; |
| entry.cookie = 0; |
| status = rx_.write(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to write to rx fifo: " << status; |
| return; |
| } |
| |
| eth_device_->Start(&status); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to start ethernet device: " << status; |
| return; |
| } |
| } |
| |
| void MockNetstack::SetInterfaceAddress(uint32_t nicid, |
| fuchsia::net::IpAddress addr, |
| uint8_t prefixLen, |
| SetInterfaceAddressCallback callback) { |
| fuchsia::netstack::NetErr err; |
| |
| if (nicid != kMockNicId) { |
| 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(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, kGuestMacAddress, 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); |
| } |
| |
| zx_status_t MockNetstack::SendUdpPacket(void* packet, size_t length) const { |
| 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) + length; |
| size_t total_length = sizeof(ethhdr) + sizeof(iphdr) + packet_length; |
| if (total_length > kMtu) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| uint8_t data[kMtu]; |
| size_t header_len = make_ip_header(kPacketTypeUdp, packet_length, data); |
| |
| uintptr_t off = header_len; |
| auto udp = reinterpret_cast<udp_hdr_t*>(data + off); |
| udp->src_port = htons(kTestPort); |
| udp->dst_port = htons(kTestPort); |
| udp->length = htons(sizeof(udp_hdr_t) + length); |
| // The checksum is optional for IPv4. |
| udp->checksum = 0; |
| |
| off += sizeof(udp_hdr_t); |
| memcpy(data + off, packet, length); |
| |
| return SendPacket(data, total_length); |
| } |
| |
| zx_status_t MockNetstack::SendPacket(void* packet, size_t length) const { |
| if (length > kMtu) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| eth_fifo_entry_t entry; |
| entry.offset = kMtu; |
| entry.length = length; |
| entry.flags = 0; |
| entry.cookie = 0; |
| size_t count; |
| memcpy(reinterpret_cast<void*>(io_addr_ + entry.offset), packet, length); |
| zx_status_t status = tx_.write(sizeof(eth_fifo_entry_t), &entry, 1, &count); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (count != 1) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| zx_signals_t pending = 0; |
| status = tx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, |
| zx::deadline_after(kTestTimeout), &pending); |
| if (status != ZX_OK) { |
| return status; |
| } else if (pending & ZX_SOCKET_PEER_CLOSED) { |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| status = tx_.read(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (entry.flags != ETH_FIFO_TX_OK) { |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t MockNetstack::ReceivePacket(void* packet, size_t length, |
| size_t* actual) const { |
| eth_fifo_entry_t entry; |
| |
| zx_signals_t pending = 0; |
| zx_status_t status = rx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, |
| zx::deadline_after(kTestTimeout), &pending); |
| if (status != ZX_OK) { |
| return status; |
| } else if (pending & ZX_SOCKET_PEER_CLOSED) { |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| status = rx_.read(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (entry.flags != ETH_FIFO_RX_OK) { |
| return ZX_ERR_IO; |
| } |
| if (entry.length > length) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| memcpy(packet, reinterpret_cast<void*>(io_addr_ + entry.offset), length); |
| *actual = entry.length; |
| |
| memset(reinterpret_cast<void*>(io_addr_), 0, kMtu); |
| entry.offset = 0; |
| entry.length = kMtu; |
| entry.flags = 0; |
| entry.cookie = 0; |
| status = rx_.write(sizeof(eth_fifo_entry_t), &entry, 1, nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to write to rx fifo: " << status; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |