blob: 2db8cbd26d170086cceb113cd8eb00970b3b08b3 [file] [log] [blame]
// Copyright 2017 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 <inet6/inet6.h>
#include <zircon/device/ethernet.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#define SRC_PORT 5004
#define DST_PORT 5005
#define BUFSIZE 2048
#define BUFS 256
#define RX_FIFO 0
#define TX_FIFO 1
typedef struct eth_buf eth_buf_t;
struct eth_buf {
eth_buf_t* next;
eth_fifo_entry_t* e;
};
typedef struct {
mac_addr_t dst;
mac_addr_t src;
uint16_t type;
} __PACKED eth_hdr_t;
eth_buf_t* avail_tx_buffers = NULL;
eth_buf_t* pending_tx = NULL;
zx_handle_t port = ZX_HANDLE_INVALID;
void flip_src_dst(void* packet) {
eth_hdr_t* eth = packet;
mac_addr_t src_mac = eth->src;
eth->src = eth->dst;
eth->dst = src_mac;
ip6_hdr_t* ip = packet + ETH_HDR_LEN;
ip6_addr_t src_ip = ip->src;
ip->src = ip->dst;
ip->dst = src_ip;
ip->next_header = HDR_UDP;
udp_hdr_t* udp = packet + ETH_HDR_LEN + IP6_HDR_LEN;
uint16_t src_port = udp->src_port;
udp->src_port = udp->dst_port;
udp->dst_port = src_port;
udp->checksum = 0;
udp->checksum = ip6_checksum(ip, HDR_UDP, ntohs(ip->length));
}
void send_pending_tx(zx_handle_t tx_fifo) {
uint32_t n;
zx_status_t status;
while (pending_tx != NULL) {
eth_fifo_entry_t* e = pending_tx->e;
e->cookie = pending_tx;
if ((status = zx_fifo_write(tx_fifo, e, sizeof(eth_fifo_entry_t), &n)) != ZX_OK) {
fprintf(stderr, "netreflector: error reflecting packet %d\n", status);
return;
}
pending_tx = pending_tx->next;
}
}
void tx_complete(eth_fifo_entry_t* e) {
if (e->flags & ETH_FIFO_TX_OK) {
eth_buf_t* buf = e->cookie;
buf->next = avail_tx_buffers;
avail_tx_buffers = buf;
}
}
zx_status_t acquire_tx_buffer(eth_buf_t** out) {
if (avail_tx_buffers == NULL) {
fprintf(stderr, "netreflector: no tx buffers available.\n");
return ZX_ERR_SHOULD_WAIT;
}
*out = avail_tx_buffers;
avail_tx_buffers = avail_tx_buffers->next;
return ZX_OK;
}
void queue_tx_buffer(eth_buf_t* tx) {
tx->next = pending_tx;
pending_tx = tx;
}
zx_status_t reflect_packet(char* iobuf, eth_fifo_entry_t* e) {
eth_buf_t* tx;
zx_status_t status;
if ((status = acquire_tx_buffer(&tx)) != ZX_OK) {
return status;
}
tx->e->length = e->length;
void* in_pkt = iobuf + e->offset;
void* out_pkt = iobuf + tx->e->offset;
memcpy(out_pkt, in_pkt, tx->e->length);
flip_src_dst(out_pkt);
queue_tx_buffer(tx);
return ZX_OK;
}
void rx_complete(char* iobuf, zx_handle_t rx_fifo, eth_fifo_entry_t* e) {
if (!(e->flags & ETH_FIFO_RX_OK)) {
return;
}
if (e->length < ETH_HDR_LEN + IP6_HDR_LEN + UDP_HDR_LEN) {
goto queue;
}
// Only reflect packets arriving from DST_PORT on SRC_PORT.
void* in_pkt = iobuf + e->offset;
udp_hdr_t* udp = in_pkt + ETH_HDR_LEN + IP6_HDR_LEN;
if (ntohs(udp->dst_port) != DST_PORT || ntohs(udp->src_port) != SRC_PORT) {
goto queue;
}
reflect_packet(iobuf, e);
queue:
e->length = BUFSIZE;
e->flags = 0;
uint32_t actual;
zx_status_t status;
if ((status = zx_fifo_write(rx_fifo, e, sizeof(*e), &actual)) != ZX_OK) {
fprintf(stderr, "netreflector: failed to queue rx packet: %d\n", status);
}
}
void handle(char* iobuf, eth_fifos_t* fifos) {
zx_port_packet_t packet;
zx_status_t status;
uint32_t n;
eth_fifo_entry_t entries[BUFS];
for (;;) {
status = zx_port_wait(port, ZX_TIME_INFINITE, &packet, 0);
if (status != ZX_OK) {
fprintf(stderr, "netreflector: error while waiting on port %d\n", status);
return;
}
if (packet.signal.observed & ZX_FIFO_PEER_CLOSED) {
fprintf(stderr, "netreflector: fifo closed\n");
return;
}
if (packet.signal.observed & ZX_FIFO_READABLE) {
uint8_t fifo_id = (uint8_t)packet.key;
zx_handle_t fifo = (fifo_id == RX_FIFO ? fifos->rx_fifo : fifos->tx_fifo);
if ((status = zx_fifo_read(fifo, entries, sizeof(entries), &n)) != ZX_OK) {
fprintf(stderr, "netreflector: error reading fifo %d\n", status);
continue;
}
eth_fifo_entry_t* e = entries;
switch (fifo_id) {
case TX_FIFO:
for (uint32_t i = 0; i < n; i++, e++) {
tx_complete(e);
}
break;
case RX_FIFO:
for (uint32_t i = 0; i < n; i++, e++) {
rx_complete(iobuf, fifos->rx_fifo, e);
}
break;
default:
fprintf(stderr, "netreflector: unknown key %lu\n", packet.key);
break;
}
}
send_pending_tx(fifos->tx_fifo);
}
}
int main(int argc, char** argv) {
const char* ethdev = (argc > 1 ? argv[1] : "/dev/class/ethernet/000");
int fd;
if ((fd = open(ethdev, O_RDWR)) < 0) {
fprintf(stderr, "netreflector: cannot open '%s'\n", argv[1]);
return -1;
}
eth_fifos_t fifos;
zx_status_t status;
ssize_t r;
if ((r = ioctl_ethernet_set_client_name(fd, "netreflector", 13)) < 0) {
fprintf(stderr, "netreflector: failed to set client name %zd\n", r);
}
if ((r = ioctl_ethernet_get_fifos(fd, &fifos)) < 0) {
fprintf(stderr, "netreflector: failed to get fifos: %zd\n", r);
return r;
}
// Allocate shareable ethernet buffer data heap. The first BUFS entries represent rx buffers,
// followed by BUFS entries representing tx buffers.
unsigned count = BUFS * 2;
zx_handle_t iovmo;
if ((status = zx_vmo_create(count * BUFSIZE, 0, &iovmo)) < 0) {
return -1;
}
char* iobuf;
if ((status = zx_vmar_map(zx_vmar_root_self(), 0, iovmo, 0, count * BUFSIZE,
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE,
(uintptr_t*)&iobuf)) < 0) {
return -1;
}
if ((r = ioctl_ethernet_set_iobuf(fd, &iovmo)) < 0) {
fprintf(stderr, "netreflector: failed to set iobuf: %zd\n", r);
return -1;
}
// Write first BUFS entries to rx fifo...
unsigned n = 0;
for (; n < BUFS; n++) {
eth_fifo_entry_t entry = {
.offset = n * BUFSIZE, .length = BUFSIZE, .flags = 0, .cookie = NULL,
};
uint32_t actual;
if ((status = zx_fifo_write(fifos.rx_fifo, &entry, sizeof(entry), &actual)) < 0) {
fprintf(stderr, "netreflector: failed to queue rx packet: %d\n", status);
return -1;
}
}
// ... continue writing next BUFS entries to tx fifo.
eth_buf_t* buf = malloc(sizeof(eth_buf_t) * BUFS);
for (; n < count; n++, buf++) {
eth_fifo_entry_t entry = {
.offset = n * BUFSIZE, .length = BUFSIZE, .flags = 0, .cookie = buf,
};
buf->e = &entry;
buf->next = avail_tx_buffers;
avail_tx_buffers = buf;
}
if (ioctl_ethernet_start(fd) < 0) {
fprintf(stderr, "netreflector: failed to start network interface\n");
return -1;
}
if (zx_port_create(0, &port) != ZX_OK) {
fprintf(stderr, "netreflector: failed to create port\n");
return -1;
}
u_int32_t signals = ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED;
if ((status = zx_object_wait_async(fifos.rx_fifo, port, RX_FIFO, signals,
ZX_WAIT_ASYNC_REPEATING)) != ZX_OK) {
fprintf(stderr, "netreflector: failed binding port to rx fifo %d\n", status);
return -1;
}
if ((status = zx_object_wait_async(fifos.tx_fifo, port, TX_FIFO, signals,
ZX_WAIT_ASYNC_REPEATING)) != ZX_OK) {
fprintf(stderr, "netreflector: failed binding port to tx fifo %d\n", status);
return -1;
}
handle(iobuf, &fifos);
return 0;
}