707 lines
24 KiB
C
707 lines
24 KiB
C
#include <devif/queue_interface_backend.h>
|
|
#include <devif/backends/net/enet_devif.h>
|
|
#include <aos/aos.h>
|
|
#include <driverkit/driverkit.h>
|
|
#include <dev/imx8x/enet_dev.h>
|
|
|
|
#include <aos/ump_binding.h>
|
|
#include <aos/ump_chan.h>
|
|
#include <aos/ump_net.h>
|
|
|
|
#include <netutil/etharp.h>
|
|
#include <netutil/ip.h>
|
|
#include <netutil/icmp.h>
|
|
#include <netutil/udp.h>
|
|
#include <netutil/checksum.h>
|
|
#include <netutil/types.h>
|
|
|
|
#include "enet.h"
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc6335#section-6
|
|
#define EPHEMERAL_PORT_START 49152
|
|
#define EPHEMERAL_PORT_END 65535
|
|
|
|
extern struct enet_driver_state *st;
|
|
|
|
static void write_eth_ip_header (
|
|
struct devq_buf *buf, void *vaddr,
|
|
uint64_t eth_dst, uint32_t ip_dest, uint8_t proto, size_t payload_len
|
|
);
|
|
|
|
static void tx_alloc (struct tx_alloc_queue_entry *entry, tx_alloc_callback_t cb, void *cb_arg) {
|
|
errval_t err = DEVQ_ERR_QUEUE_EMPTY;
|
|
struct devq_buf buf;
|
|
|
|
// If the queue is not empty, always append to the queue, so that FIFO order is maintained
|
|
if (st->tx_alloc_queue_head == NULL) {
|
|
err = devq_dequeue((struct devq*) st->txq, &buf.rid, &buf.offset,
|
|
&buf.length, &buf.valid_data, &buf.valid_length,
|
|
&buf.flags);
|
|
}
|
|
if (err_is_fail(err)) {
|
|
if (st->tx_init_i < st->txq->size) {
|
|
buf.rid = st->tx_rid;
|
|
buf.offset = st->tx_init_i * 2048;
|
|
buf.length = 2048;
|
|
buf.valid_data = 0;
|
|
buf.valid_length = 2048;
|
|
buf.flags = 0;
|
|
st->tx_init_i++;
|
|
} else {
|
|
entry->cb = cb;
|
|
entry->cb_arg = cb_arg;
|
|
entry->next = NULL;
|
|
if (st->tx_alloc_queue_tail == NULL) {
|
|
st->tx_alloc_queue_head = entry;
|
|
} else {
|
|
st->tx_alloc_queue_tail->next = entry;
|
|
}
|
|
st->tx_alloc_queue_tail = entry;
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct region_entry *region_entry = enet_get_region(st->txq, buf.rid);
|
|
assert(region_entry != NULL);
|
|
void *vaddr = (void*)region_entry->mem.vbase + buf.offset;
|
|
cb(cb_arg, &buf, vaddr);
|
|
}
|
|
|
|
static void tx_send (struct devq_buf *buf) {
|
|
errval_t err;
|
|
err = devq_enqueue((struct devq*) st->txq, buf->rid, buf->offset,
|
|
buf->length, buf->valid_data, buf->valid_length,
|
|
buf->flags);
|
|
assert(err_is_ok(err));
|
|
}
|
|
|
|
static void rx_release (struct devq_buf *buf) {
|
|
errval_t err;
|
|
err = devq_enqueue((struct devq*) st->rxq, buf->rid, buf->offset,
|
|
buf->length, buf->valid_data, buf->valid_length,
|
|
buf->flags);
|
|
assert(err_is_ok(err));
|
|
}
|
|
|
|
// ARP: https://datatracker.ietf.org/doc/html/rfc826
|
|
|
|
static void arp_send (
|
|
struct devq_buf *buf, void *vaddr,
|
|
uint64_t eth_dst, uint32_t ip_dest,
|
|
uint16_t op
|
|
) {
|
|
buf->valid_data = 0;
|
|
buf->valid_length = ETH_HLEN + sizeof(struct arp_hdr);
|
|
|
|
struct eth_hdr *eth_hdr = vaddr;
|
|
eth_hdr->dst = eth_addr_wr(eth_dst);
|
|
eth_hdr->src = eth_addr_wr(st->mac);
|
|
eth_hdr->type = uint16_wr(ETH_TYPE_ARP);
|
|
|
|
struct arp_hdr *arp_hdr = vaddr + ETH_HLEN;
|
|
arp_hdr->hwtype = uint16_wr(ARP_HW_TYPE_ETH);
|
|
arp_hdr->proto = uint16_wr(ETH_TYPE_IP);
|
|
arp_hdr->hwlen = ETH_ADDR_LEN;
|
|
arp_hdr->protolen = IP_ADDR_LEN;
|
|
arp_hdr->opcode = uint16_wr(op);
|
|
arp_hdr->eth_src = eth_addr_wr(st->mac);
|
|
arp_hdr->ip_src = uint32_wr(st->my_ip);
|
|
arp_hdr->eth_dst = eth_addr_wr(eth_dst);
|
|
arp_hdr->ip_dst = uint32_wr(ip_dest);
|
|
|
|
tx_send(buf);
|
|
}
|
|
|
|
static void arp_reply (void *cb_arg, struct devq_buf *buf, void *vaddr) {
|
|
struct arp_tx_queue_entry *entry = &st->arp.tx_queue[st->arp.tx_queue_tail];
|
|
assert(cb_arg == entry);
|
|
|
|
arp_send(buf, vaddr, entry->eth_dst, entry->ip_dst, ARP_OP_REP);
|
|
|
|
st->arp.tx_queue_tail = (st->arp.tx_queue_tail + 1) & (ARP_TX_QUEUE_LEN - 1);
|
|
st->arp.tx_queue_len--;
|
|
}
|
|
|
|
static void arp_handle (struct devq_buf *buf, void *vaddr, struct eth_hdr *eth_hdr) {
|
|
ENET_DEBUG("Received ARP packet\n");
|
|
|
|
if (buf->valid_length < sizeof(struct arp_hdr)) {
|
|
ENET_WARN("Received ARP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
struct arp_hdr *arp_hdr = vaddr + buf->valid_data;
|
|
uint16_t opcode = uint16_rd(arp_hdr->opcode);
|
|
if (
|
|
uint16_rd(arp_hdr->hwtype) != ARP_HW_TYPE_ETH ||
|
|
uint16_rd(arp_hdr->proto) != ETH_TYPE_IP ||
|
|
arp_hdr->hwlen != ETH_ADDR_LEN ||
|
|
arp_hdr->protolen != IP_ADDR_LEN ||
|
|
(opcode != ARP_OP_REQ && opcode != ARP_OP_REP)
|
|
) {
|
|
ENET_WARN("Unknown ARP packet type\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
uint32_t ip_src = uint32_rd(arp_hdr->ip_src);
|
|
|
|
if ((st->my_ip & st->net_mask) != (ip_src & st->net_mask)) {
|
|
ENET_WARN("Received ARP packet for address outside my network\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
uint64_t eth_src = eth_addr_rd(arp_hdr->eth_src);
|
|
uint64_t eth_old = (uintptr_t)collections_hash_find(st->arp.table, ip_src);
|
|
// check if sender protocol address is in translation table, if so update entry
|
|
if (eth_old != 0 && eth_src != eth_old) {
|
|
ENET_WARN("MAC address of %d.%d.%d.%d has changed\n",
|
|
arp_hdr->ip_src.val[0],
|
|
arp_hdr->ip_src.val[1],
|
|
arp_hdr->ip_src.val[2],
|
|
arp_hdr->ip_src.val[3]
|
|
);
|
|
collections_hash_delete(st->arp.table, ip_src);
|
|
collections_hash_insert(st->arp.table, ip_src, (void*)eth_src);
|
|
}
|
|
|
|
if (uint32_rd(arp_hdr->ip_dst) == st->my_ip) {
|
|
ENET_DEBUG("Received ARP packet addressed to me\n");
|
|
|
|
// if sender protocol address was not in translation table, add entry
|
|
if (eth_old == 0) {
|
|
collections_hash_insert(st->arp.table, ip_src, (void*)eth_src);
|
|
}
|
|
|
|
if (opcode == ARP_OP_REQ) {
|
|
if (st->arp.tx_queue_len >= ARP_TX_QUEUE_LEN) {
|
|
ENET_WARN("Too many queued ARP replies, not sending reply\n");
|
|
} else {
|
|
struct arp_tx_queue_entry *entry = &st->arp.tx_queue[(st->arp.tx_queue_tail + st->arp.tx_queue_len) & (ARP_TX_QUEUE_LEN - 1)];
|
|
st->arp.tx_queue_len++;
|
|
entry->eth_dst = eth_src;
|
|
entry->ip_dst = ip_src;
|
|
tx_alloc(&entry->tx_entry, arp_reply, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
rx_release(buf);
|
|
}
|
|
|
|
// ICMP: https://datatracker.ietf.org/doc/html/rfc792
|
|
|
|
static void icmp_reply (void *cb_arg, struct devq_buf *buf, void *vaddr) {
|
|
struct icmp_echo_reply_meta *entry = cb_arg;
|
|
|
|
write_eth_ip_header(
|
|
buf, vaddr,
|
|
eth_addr_rd(entry->eth_hdr->src),
|
|
uint32_rd(entry->ip_hdr->src),
|
|
IP_PROTO_ICMP,
|
|
entry->rx_buf.valid_length
|
|
);
|
|
|
|
struct icmp_echo_hdr *icmp_echo_hdr = vaddr + ETH_HLEN + sizeof(struct ip_hdr);
|
|
memcpy(icmp_echo_hdr, entry->icmp_echo_hdr, entry->rx_buf.valid_length);
|
|
icmp_echo_hdr->type = ICMP_ER;
|
|
|
|
// Incremental update: https://datatracker.ietf.org/doc/html/rfc1071
|
|
uint32_t chksum = uint16_rd(icmp_echo_hdr->chksum);
|
|
chksum += (ICMP_ECHO << 8) + ((ICMP_ER << 8) ^ 0x0000ffffUL);
|
|
chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
|
|
icmp_echo_hdr->chksum = uint16_wr(chksum);
|
|
|
|
rx_release(&entry->rx_buf);
|
|
simpleslab_free(&st->rx_meta_slab, entry);
|
|
tx_send(buf);
|
|
}
|
|
|
|
static void icmp_handle (struct devq_buf *buf, void *vaddr, struct eth_hdr *eth_hdr, struct ip_hdr *ip_hdr) {
|
|
if (buf->valid_length < 4) {
|
|
ENET_WARN("Received ICMP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
if (inet_checksum(vaddr + buf->valid_data, buf->valid_length) != 0) {
|
|
ENET_WARN("Received ICMP packet with bad checksum\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
uint8_t type = *(uint8_t*)(vaddr + buf->valid_data);
|
|
if (type == ICMP_ECHO) {
|
|
if (buf->valid_length < sizeof(struct icmp_echo_hdr)) {
|
|
ENET_WARN("Received ICMP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
struct icmp_echo_hdr *icmp_echo_hdr = vaddr + buf->valid_data;
|
|
ENET_DEBUG("Received ICMP echo, seq=%d\n", uint16_rd(icmp_echo_hdr->seqno));
|
|
|
|
struct icmp_echo_reply_meta *meta = simpleslab_alloc(&st->rx_meta_slab);
|
|
meta->rx_buf = *buf;
|
|
meta->eth_hdr = eth_hdr;
|
|
meta->ip_hdr = ip_hdr;
|
|
meta->icmp_echo_hdr = icmp_echo_hdr;
|
|
|
|
tx_alloc(&meta->tx_entry, icmp_reply, meta);
|
|
return;
|
|
} else {
|
|
ENET_WARN("Received ICMP packet with unknown type %d\n", type);
|
|
}
|
|
|
|
rx_release(buf);
|
|
}
|
|
|
|
// UDP: https://datatracker.ietf.org/doc/html/rfc768
|
|
|
|
static void udp_recv_ump_callback (void *arg, struct ump_send_queue_entry *entry) {
|
|
struct udp_recv *meta = arg;
|
|
rx_release(&meta->rx_buf);
|
|
simpleslab_free(&st->rx_meta_slab, meta);
|
|
}
|
|
|
|
static uint16_t udp_checksum (struct ip_hdr *ip_hdr, struct udp_hdr *udp_hdr) {
|
|
uint16_t udp_len = uint16_rd(udp_hdr->len);
|
|
uint32_t chksum = inet_checksum(udp_hdr, udp_len) ^ 0x0000ffffUL;
|
|
// add pseudo header
|
|
chksum += (ip_hdr->src.val[0] << 8) | ip_hdr->src.val[1];
|
|
chksum += (ip_hdr->src.val[2] << 8) | ip_hdr->src.val[3];
|
|
chksum += (ip_hdr->dest.val[0] << 8) | ip_hdr->dest.val[1];
|
|
chksum += (ip_hdr->dest.val[2] << 8) | ip_hdr->dest.val[3];
|
|
chksum += IP_PROTO_UDP;
|
|
chksum += udp_len;
|
|
chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
|
|
chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
|
|
return ~(uint16_t)chksum;
|
|
}
|
|
|
|
static void udp_handle (struct devq_buf *buf, void *vaddr, struct eth_hdr *eth_hdr, struct ip_hdr *ip_hdr) {
|
|
if (buf->valid_length < UDP_HLEN) {
|
|
ENET_WARN("Received UDP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
struct udp_hdr *udp_hdr = vaddr + buf->valid_data;
|
|
uint16_t udp_len = uint16_rd(udp_hdr->len);
|
|
if (udp_len < UDP_HLEN || buf->valid_length < udp_len) {
|
|
ENET_WARN("Received UDP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
uint16_rd(udp_hdr->chksum) != 0 && // 0 means no checksum
|
|
udp_checksum(ip_hdr, udp_hdr) != 0
|
|
) {
|
|
ENET_WARN("Received UDP packet with bad checksum\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
uint16_t src_port = uint16_rd(udp_hdr->src);
|
|
uint16_t dest_port = uint16_rd(udp_hdr->dest);
|
|
void *payload = (void*)udp_hdr + UDP_HLEN;
|
|
uint16_t payload_len = udp_len - UDP_HLEN;
|
|
ENET_DEBUG("Received UDP packet from %d to %d, len %d\n", src_port, dest_port, payload_len);
|
|
|
|
|
|
struct ump_client *client = collections_hash_find(st->udp.listen_table, dest_port);
|
|
if (client == NULL) {
|
|
// TODO: send ICMP unreachable
|
|
rx_release(buf);
|
|
} else {
|
|
struct udp_recv *meta = simpleslab_alloc(&st->rx_meta_slab);
|
|
meta->rx_buf = *buf;
|
|
meta->ump_header = (struct ump_net_in_header){
|
|
.op = UMP_NET_EV_UDP_RECV,
|
|
.d = { .udp_recv = {
|
|
.src_ip = uint32_rd(ip_hdr->src),
|
|
.dest_ip = uint32_rd(ip_hdr->dest),
|
|
.src_port = src_port,
|
|
.dest_port = dest_port,
|
|
} }
|
|
};
|
|
ump_send(client->send_chan, &meta->ump_entry,
|
|
sizeof(struct ump_net_in_header), &meta->ump_header,
|
|
payload_len, payload,
|
|
udp_recv_ump_callback, meta);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// IP: https://datatracker.ietf.org/doc/html/rfc791#section-3.1
|
|
|
|
static void ip_handle (struct devq_buf *buf, void *vaddr, struct eth_hdr *eth_hdr) {
|
|
if (buf->valid_length < sizeof(struct ip_hdr)) {
|
|
ENET_WARN("Received IP packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
struct ip_hdr *ip_hdr = vaddr + buf->valid_data;
|
|
uint16_t ip_len = uint16_rd(ip_hdr->len);
|
|
uint16_t header_len = IPH_HL(ip_hdr) * 4;
|
|
if (
|
|
IPH_V(ip_hdr) != 4 ||
|
|
header_len < 20 ||
|
|
header_len > ip_len ||
|
|
ip_len > buf->valid_length
|
|
) {
|
|
ENET_WARN("Received bad IP packet\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
if (inet_checksum(ip_hdr, header_len) != 0) {
|
|
ENET_WARN("Received IP packet with bad checksum\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
uint16_t offset_flags = uint16_rd(ip_hdr->offset);
|
|
if (
|
|
(offset_flags & IP_MF) != 0 ||
|
|
(offset_flags & IP_OFFMASK) != 0
|
|
) {
|
|
ENET_WARN("Received IP fragment, dropping\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
ENET_DEBUG("Received IP packet from %d.%d.%d.%d to %d.%d.%d.%d, proto %d\n",
|
|
ip_hdr->src.val[0],
|
|
ip_hdr->src.val[1],
|
|
ip_hdr->src.val[2],
|
|
ip_hdr->src.val[3],
|
|
ip_hdr->dest.val[0],
|
|
ip_hdr->dest.val[1],
|
|
ip_hdr->dest.val[2],
|
|
ip_hdr->dest.val[3],
|
|
ip_hdr->proto
|
|
);
|
|
|
|
buf->valid_data += header_len;
|
|
buf->valid_length = ip_len - header_len;
|
|
|
|
if (uint32_rd(ip_hdr->dest) == st->my_ip) {
|
|
if (ip_hdr->proto == IP_PROTO_ICMP) {
|
|
icmp_handle(buf, vaddr, eth_hdr, ip_hdr);
|
|
} else if (ip_hdr->proto == IP_PROTO_UDP) {
|
|
udp_handle(buf, vaddr, eth_hdr, ip_hdr);
|
|
} else {
|
|
ENET_WARN("Received IP packet with unknown protocol %d\n", ip_hdr->proto);
|
|
rx_release(buf);
|
|
}
|
|
} else {
|
|
ENET_WARN("Received IP packet for someone else\n");
|
|
rx_release(buf);
|
|
}
|
|
}
|
|
|
|
static void write_eth_ip_header (
|
|
struct devq_buf *buf, void *vaddr,
|
|
uint64_t eth_dst, uint32_t ip_dest, uint8_t proto, size_t payload_len
|
|
) {
|
|
buf->valid_data = 0;
|
|
buf->valid_length = ETH_HLEN + sizeof(struct ip_hdr) + payload_len;
|
|
assert(buf->valid_length <= ETH_HLEN + 1500);
|
|
|
|
struct eth_hdr *eth_hdr = vaddr;
|
|
eth_hdr->dst = eth_addr_wr(eth_dst);
|
|
eth_hdr->src = eth_addr_wr(st->mac);
|
|
eth_hdr->type = uint16_wr(ETH_TYPE_IP);
|
|
|
|
struct ip_hdr *ip_hdr = vaddr + ETH_HLEN;
|
|
IPH_VHL_SET(ip_hdr, 4, 5);
|
|
ip_hdr->tos = 0;
|
|
ip_hdr->len = uint16_wr(sizeof(struct ip_hdr) + payload_len);
|
|
ip_hdr->id = uint16_wr(st->ip.next_id++);
|
|
ip_hdr->offset = uint16_wr(IP_DF);
|
|
ip_hdr->ttl = 64;
|
|
ip_hdr->proto = proto;
|
|
ip_hdr->chksum = uint16_wr(0);
|
|
ip_hdr->src = uint32_wr(st->my_ip);
|
|
ip_hdr->dest = uint32_wr(ip_dest);
|
|
ip_hdr->chksum = uint16_wr(inet_checksum(ip_hdr, 20));
|
|
}
|
|
|
|
// Ethernet
|
|
|
|
static void rx_handle (struct devq_buf *buf) {
|
|
struct region_entry *entry = enet_get_region(st->rxq, buf->rid);
|
|
assert(entry != NULL);
|
|
void *vaddr = (void*)entry->mem.vbase + buf->offset;
|
|
void *eth_vaddr = vaddr + buf->valid_data;
|
|
|
|
#if defined(ENET_DEBUG_OPTION)
|
|
debug_printf("Received Packet of size %lu:", buf->valid_length);
|
|
for (size_t i = 0; i < buf->valid_length; i++) {
|
|
printf(" %02x", ((uint8_t*)eth_vaddr)[i]);
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
|
|
if (buf->valid_length < ETH_HLEN) {
|
|
ENET_WARN("Ethernet packet too small\n");
|
|
rx_release(buf);
|
|
return;
|
|
}
|
|
|
|
struct eth_hdr *eth_hdr = eth_vaddr;
|
|
uint16_t type = uint16_rd(eth_hdr->type);
|
|
|
|
buf->valid_data += ETH_HLEN;
|
|
buf->valid_length -= ETH_HLEN;
|
|
|
|
if (type == ETH_TYPE_ARP) {
|
|
arp_handle(buf, vaddr, eth_hdr);
|
|
} else if (type == ETH_TYPE_IP) {
|
|
ip_handle(buf, vaddr, eth_hdr);
|
|
} else {
|
|
ENET_WARN("Received packet of unknown type: %04x\n", type);
|
|
rx_release(buf);
|
|
}
|
|
}
|
|
|
|
// UMP
|
|
|
|
static void netump_header_handle (void *arg, size_t header_size, void *header_raw, size_t payload_size);
|
|
|
|
static void netump_next (struct ump_client *client) {
|
|
if (client->ump_reply_slab.free != 0) {
|
|
ump_recv_header(client->recv_chan, netump_header_handle, client);
|
|
} else {
|
|
client->blocked_on_reply_slab = true;
|
|
}
|
|
}
|
|
|
|
static void netump_reply_callback (void *arg, struct ump_send_queue_entry *entry) {
|
|
struct ump_reply_buf *reply = arg;
|
|
struct ump_client *client = reply->client;
|
|
simpleslab_free(&client->ump_reply_slab, reply);
|
|
if (client->blocked_on_reply_slab) {
|
|
client->blocked_on_reply_slab = false;
|
|
netump_next(client);
|
|
}
|
|
}
|
|
|
|
static void netump_payload_ignore (void *arg, size_t payload_size, void *payload) {
|
|
netump_next(arg);
|
|
}
|
|
|
|
static void netump_send_reply (struct ump_reply_buf *reply, errval_t err) {
|
|
reply->ump_header.ret_err = err;
|
|
ump_send(reply->client->send_chan, &reply->ump_entry,
|
|
sizeof(struct ump_net_in_header), &reply->ump_header,
|
|
0, NULL,
|
|
netump_reply_callback, reply);
|
|
}
|
|
|
|
static void netump_udp_tx_payload (void *arg, size_t payload_size, void *payload) {
|
|
struct ump_client *client = arg;
|
|
|
|
struct udp_hdr *udp_hdr = (void*)client->tx_ip_hdr + sizeof(struct ip_hdr);
|
|
uint16_t checksum = udp_checksum(client->tx_ip_hdr, udp_hdr);
|
|
if (checksum == 0) checksum = 0xffff;
|
|
udp_hdr->chksum = uint16_wr(checksum);
|
|
|
|
tx_send(&client->tx_buf);
|
|
netump_next(client);
|
|
}
|
|
|
|
static void netump_udp_tx_allocated (void *arg, struct devq_buf *buf, void *vaddr) {
|
|
struct ump_reply_buf *reply = arg;
|
|
struct ump_client *client = reply->client;
|
|
struct ump_net_out_header *header = client->recv_chan->header;
|
|
size_t payload_size = client->recv_chan->next_payload_size;
|
|
|
|
uint64_t eth_dest = (uintptr_t)collections_hash_find(st->arp.table, header->d.udp_send.dest_ip);
|
|
if (eth_dest == 0) {
|
|
ENET_WARN("IP not in ARP table, dropping packet and sending ARP request\n");
|
|
arp_send(buf, vaddr, 0xffffffffffff,
|
|
header->d.udp_send.dest_ip, ARP_OP_REQ);
|
|
netump_send_reply(reply, LIB_ERR_NET_ARP_MISS);
|
|
ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
|
|
return;
|
|
}
|
|
write_eth_ip_header(
|
|
buf, vaddr,
|
|
eth_dest,
|
|
header->d.udp_send.dest_ip,
|
|
IP_PROTO_UDP,
|
|
UDP_HLEN + payload_size
|
|
);
|
|
struct udp_hdr *udp_hdr = vaddr + ETH_HLEN + sizeof(struct ip_hdr);
|
|
udp_hdr->src = uint16_wr(header->d.udp_send.src_port);
|
|
udp_hdr->dest = uint16_wr(header->d.udp_send.dest_port);
|
|
udp_hdr->len = uint16_wr(UDP_HLEN + payload_size);
|
|
udp_hdr->chksum = uint16_wr(0);
|
|
void *payload = (void*)udp_hdr + UDP_HLEN;
|
|
client->tx_buf = *buf;
|
|
client->tx_ip_hdr = vaddr + ETH_HLEN;
|
|
|
|
netump_send_reply(reply, SYS_ERR_OK);
|
|
ump_recv_payload(client->recv_chan, payload, netump_udp_tx_payload, client);
|
|
}
|
|
|
|
static void netump_header_handle (void *arg, size_t header_size, void *header_raw, size_t payload_size) {
|
|
struct ump_client *client = arg;
|
|
struct ump_net_out_header *header = header_raw;
|
|
errval_t err;
|
|
|
|
struct ump_reply_buf *reply = simpleslab_alloc(&client->ump_reply_slab);
|
|
assert(reply != NULL);
|
|
reply->client = client;
|
|
reply->ump_header.op = header->op;
|
|
|
|
if (header->op == UMP_NET_OP_UDP_SEND) {
|
|
if (payload_size > 1500 - sizeof(struct ip_hdr) - UDP_HLEN) {
|
|
err = LIB_ERR_NET_PACKET_TOO_BIG;
|
|
} else {
|
|
tx_alloc(&client->tx_entry, netump_udp_tx_allocated, reply);
|
|
return;
|
|
}
|
|
} else if (header->op == UMP_NET_OP_UDP_LISTEN) {
|
|
uint16_t port = header->d.udp_listen.dest_port;
|
|
if (port == 0) {
|
|
// allocate an ephemeral port
|
|
uint16_t first = st->udp.next_ephemeral_port;
|
|
port = first;
|
|
while (true) {
|
|
uint16_t next_port;
|
|
if (port == EPHEMERAL_PORT_END) {
|
|
next_port = EPHEMERAL_PORT_START;
|
|
} else {
|
|
next_port = port + 1;
|
|
}
|
|
if (collections_hash_find(st->udp.listen_table, port) == NULL) {
|
|
st->udp.next_ephemeral_port = next_port;
|
|
break;
|
|
}
|
|
port = next_port;
|
|
if (port == first) {
|
|
port = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (port == 0) {
|
|
err = LIB_ERR_NET_ALLOC_PORT;
|
|
} else if (collections_hash_find(st->udp.listen_table, port) != NULL) {
|
|
err = LIB_ERR_NET_PORT_IN_USE;
|
|
} else {
|
|
ENET_DEBUG("Listening on UDP port %d\n", port);
|
|
reply->ump_header.d.udp_listen.dest_port = port;
|
|
collections_hash_insert(st->udp.listen_table, port, client);
|
|
err = SYS_ERR_OK;
|
|
}
|
|
} else if (header->op == UMP_NET_OP_UDP_LISTEN_STOP) {
|
|
uint16_t port = header->d.udp_listen_stop.dest_port;
|
|
if (collections_hash_find(st->udp.listen_table, port) != client) {
|
|
err = LIB_ERR_NET_NOT_LISTENING;
|
|
} else {
|
|
ENET_DEBUG("Stopped listening on UDP port %d\n", port);
|
|
collections_hash_delete(st->udp.listen_table, port);
|
|
err = SYS_ERR_OK;
|
|
}
|
|
} else {
|
|
debug_printf("Error: unknown UMP op\n");
|
|
simpleslab_free(&client->ump_reply_slab, reply);
|
|
ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
|
|
return;
|
|
}
|
|
|
|
netump_send_reply(reply, err);
|
|
ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
|
|
}
|
|
|
|
static errval_t netump_connect (void *arg, struct capref cap) {
|
|
errval_t err;
|
|
|
|
ENET_DEBUG("Incoming UMP connection\n");
|
|
|
|
struct ump_client *client = malloc(sizeof(struct ump_client));
|
|
if (client == NULL) return LIB_ERR_MALLOC_FAIL;
|
|
|
|
err = ump_chan_init(UMP_ROLE_SERVER, &client->send_chan, &client->recv_chan,
|
|
sizeof(struct ump_net_out_header), cap, get_default_waitset());
|
|
if (err_is_fail(err)) return err;
|
|
|
|
simpleslab_init(&client->ump_reply_slab, sizeof(struct ump_reply_buf),
|
|
&client->ump_reply_slab_buf, sizeof(client->ump_reply_slab_buf));
|
|
client->blocked_on_reply_slab = false;
|
|
|
|
netump_next(client);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
void enet_loop (void) {
|
|
errval_t err;
|
|
|
|
st->arp.tx_queue_len = 0;
|
|
st->arp.tx_queue_tail = 0;
|
|
collections_hash_create(&st->arp.table, NULL);
|
|
|
|
st->ip.next_id = 0;
|
|
|
|
collections_hash_create_with_buckets(&st->udp.listen_table, 100, NULL);
|
|
st->udp.next_ephemeral_port = EPHEMERAL_PORT_START;
|
|
|
|
simpleslab_init(&st->rx_meta_slab, sizeof(union rx_meta_slab_data),
|
|
&st->rx_meta_slab_buf, sizeof(st->rx_meta_slab_buf));
|
|
|
|
struct ump_binding_server server;
|
|
err = ump_binding_register(&server, UMP_SERVER_NET, netump_connect, NULL);
|
|
if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to register UMP server");
|
|
|
|
struct devq_buf buf;
|
|
struct waitset *default_ws = get_default_waitset();
|
|
while (true) {
|
|
bool made_progress = false;
|
|
|
|
err = event_dispatch_non_block(default_ws);
|
|
if (err_is_fail(err) && err != LIB_ERR_NO_EVENT) {
|
|
DEBUG_ERR(err, "in event_dispatch");
|
|
abort();
|
|
}
|
|
// TODO: set made_progress if we handled an event. Currently,
|
|
// UMP always registers a callback, so we would always see an event.
|
|
// See TODO in poll_channels_disabled.
|
|
|
|
err = devq_dequeue((struct devq*) st->rxq, &buf.rid, &buf.offset,
|
|
&buf.length, &buf.valid_data, &buf.valid_length,
|
|
&buf.flags);
|
|
if (err_is_ok(err)) {
|
|
made_progress = true;
|
|
rx_handle(&buf);
|
|
}
|
|
|
|
if (st->tx_alloc_queue_head != NULL) {
|
|
err = devq_dequeue((struct devq*) st->txq, &buf.rid, &buf.offset,
|
|
&buf.length, &buf.valid_data, &buf.valid_length,
|
|
&buf.flags);
|
|
if (err_is_ok(err)) {
|
|
made_progress = true;
|
|
struct tx_alloc_queue_entry *entry = st->tx_alloc_queue_head;
|
|
st->tx_alloc_queue_head = entry->next;
|
|
if (entry->next == NULL) {
|
|
st->tx_alloc_queue_tail = NULL;
|
|
}
|
|
struct region_entry *region_entry = enet_get_region(st->txq, buf.rid);
|
|
assert(region_entry != NULL);
|
|
void *vaddr = (void*)region_entry->mem.vbase + buf.offset;
|
|
entry->cb(entry->cb_arg, &buf, vaddr);
|
|
}
|
|
}
|
|
|
|
if (!made_progress) {
|
|
thread_yield();
|
|
}
|
|
}
|
|
}
|