blob: 1006ff9b24563bf5d414f55d7c15f94f9cef6528 [file] [log] [blame]
// 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;
}