From 1ee20136004a47d2f2d72f4584732c9faa25b0d4 Mon Sep 17 00:00:00 2001 From: Matheus Date: Sun, 7 Apr 2024 16:39:20 -0300 Subject: [PATCH] udp: socket implementation --- kernel-src/include/kernel/net.h | 19 +- kernel-src/io/net/udp.c | 311 ++++++++++++++++++++++++++++++-- 2 files changed, 317 insertions(+), 13 deletions(-) diff --git a/kernel-src/include/kernel/net.h b/kernel-src/include/kernel/net.h index 6d3c560d..35d57a53 100644 --- a/kernel-src/include/kernel/net.h +++ b/kernel-src/include/kernel/net.h @@ -8,6 +8,11 @@ #include #include #include +#include +#include +#include +#include +#include typedef struct { void *address; @@ -53,6 +58,13 @@ typedef struct { uint32_t addr; } ipv4addr_t; +typedef struct { + union { + ipv4addr_t ipv4addr; + char unaddr[256]; + }; +} sockaddr_t; + #define NET_BROADCAST_MAC (mac_t){.address = {0xff,0xff,0xff,0xff,0xff,0xff}} #define MAC_EQUAL(m1,m2) (memcmp(m1, m2, sizeof(mac_t)) == 0) @@ -63,9 +75,14 @@ typedef struct { void arp_init(); void ipv4_init(); void udp_init(); +void netdev_init(); +void udp_process(netdev_t *netdev, void *buffer, uint32_t ip); void arp_process(netdev_t *netdev, void *buffer); int arp_lookup(netdev_t *netdev, uint32_t ip, mac_t *mac); -int udp_sendpacket(netdev_t *netdev, netdesc_t desc, uint32_t ip, uint16_t srcport, uint16_t dstport); +int udp_sendpacket(void *buffer, size_t packetsize, uint32_t ip, uint16_t srcport, uint16_t dstport, netdev_t *broadcastdev); int ipv4_sendpacket(void *buffer, size_t packetsize, uint32_t ip, int proto, netdev_t *broadcastdev); +void ipv4_process(netdev_t *netdev, void *nextbuff); +int netdev_register(netdev_t *netdev, char *name); +netdev_t *netdev_getdev(char *name); #endif diff --git a/kernel-src/io/net/udp.c b/kernel-src/io/net/udp.c index 288a928e..55dc5db1 100644 --- a/kernel-src/io/net/udp.c +++ b/kernel-src/io/net/udp.c @@ -1,23 +1,310 @@ #include #include #include +#include +#include +#include +#include +#include -int udp_allocdesc(netdev_t *netdev, size_t requestedsize, netdesc_t *desc) { - int e = netdev->allocdesc(requestedsize + sizeof(udpframe_t), desc); - desc->curroffset += sizeof(udpframe_t); - return e; -} +#define SOCKET_BUFFER (256 * 1024) + +typedef struct { + uint32_t peer; + uint16_t srcport; + uint16_t length; +} dataheader_t; + +typedef struct { + socket_t socket; + uint32_t address; + uint16_t port; + uint32_t peeraddress; + uint32_t peerport; + ringbuffer_t ringbuffer; + spinlock_t ringbufferlock; + int packetsprocessing; +} udpsocket_t; + +#define ALLOC_RANGE_START 50000 +#define ALLOC_RANGE_END 60000 +static socket_t *ports[65536]; +static spinlock_t portlock; + +int udp_sendpacket(void *buffer, size_t size, uint32_t ip, uint16_t srcport, uint16_t dstport, netdev_t *broadcastnetdev) { + void *newbuff = alloc(size + sizeof(udpframe_t)); + if (newbuff == NULL) + return ENOMEM; -int udp_sendpacket(netdev_t *netdev, netdesc_t desc, uint32_t ip, uint16_t srcport, uint16_t dstport) { - size_t len = desc.size - desc.curroffset; - desc.curroffset -= sizeof(udpframe_t); udpframe_t frame = { .srcport = cpu_to_be_w(srcport), .dstport = cpu_to_be_w(dstport), - .length = cpu_to_be_w(len), - .checksum = 0xfefe + .length = cpu_to_be_w(size + sizeof(udpframe_t)), + .checksum = 0 }; - memcpy((void *)((uintptr_t)desc.address + desc.curroffset), &frame, sizeof(udpframe_t)); - return ipv4_sendpacket(netdev, desc, ip, IPV4_PROTO_UDP); + memcpy(newbuff, &frame, sizeof(udpframe_t)); + memcpy((void *)((uintptr_t)newbuff + sizeof(udpframe_t)), buffer, size); + + int e = ipv4_sendpacket(newbuff, size + sizeof(udpframe_t), ip, IPV4_PROTO_UDP, broadcastnetdev); + free(newbuff); + return e; +} + +void udp_init() { + SPINLOCK_INIT(portlock); +} + +int udp_allocport(socket_t *socket, uint16_t *port) { + bool intstate = interrupt_set(false); + spinlock_acquire(&portlock); + if (*port == 0) { + for (*port = ALLOC_RANGE_START; *port < ALLOC_RANGE_END; ++(*port)) + if (ports[*port] == NULL) + break; + + __assert(*port != ALLOC_RANGE_END); + } + + int e = 0; + if (ports[*port] != NULL) { + e = EADDRINUSE; + goto cleanup; + } + + ports[*port] = socket; + + cleanup: + spinlock_release(&portlock); + interrupt_set(intstate); + + return e; +} + +void udp_unallocport(uint16_t port) { + __assert(port > 0); + bool intstate = interrupt_set(false); + spinlock_acquire(&portlock); + ports[port] = NULL; + spinlock_release(&portlock); + interrupt_set(intstate); +} + +void udp_process(netdev_t *netdev, void *buffer, uint32_t peer) { + udpframe_t *frame = buffer; + uint16_t srcport = be_to_cpu_w(frame->srcport); + uint16_t dstport = be_to_cpu_w(frame->dstport); + uint16_t length = be_to_cpu_w(frame->length); + + spinlock_acquire(&portlock); + + udpsocket_t *socket = (udpsocket_t *)ports[dstport]; + if (socket) + __atomic_add_fetch(&socket->packetsprocessing, 1, __ATOMIC_SEQ_CST); + + spinlock_release(&portlock); + + if (socket == NULL) // drop packet if no socket listening on port + return; + + dataheader_t header = { + .peer = peer, + .srcport = srcport, + .length = length - sizeof(udpframe_t) + }; + + void *writebuffer = (void *)((uintptr_t)buffer + sizeof(udpframe_t)); + size_t needed = header.length + sizeof(dataheader_t); + spinlock_acquire(&socket->ringbufferlock); + size_t freespace = SOCKET_BUFFER - RINGBUFFER_DATACOUNT(&socket->ringbuffer); + if (freespace >= needed) { + __assert(ringbuffer_write(&socket->ringbuffer, &header, sizeof(header)) == sizeof(header)); + __assert(ringbuffer_write(&socket->ringbuffer, writebuffer, header.length) == header.length); + poll_event(&socket->socket.pollheader, POLLOUT); + } + spinlock_release(&socket->ringbufferlock); + __atomic_sub_fetch(&socket->packetsprocessing, 1, __ATOMIC_SEQ_CST); +} + +static int udp_bind(socket_t *socket, sockaddr_t *addr) { + int e; + MUTEX_ACQUIRE(&socket->mutex, false); + if (socket->state != SOCKET_STATE_UNBOUND) { + e = EINVAL; + goto cleanup; + } + + e = udp_allocport(socket, &addr->ipv4addr.port); + + if (e) + goto cleanup; + + udpsocket_t *udpsocket = (udpsocket_t *)socket; + udpsocket->port = addr->ipv4addr.port; + udpsocket->address = addr->ipv4addr.addr; + + socket->state = SOCKET_STATE_BOUND; + + cleanup: + MUTEX_RELEASE(&socket->mutex); + return e; +} + +static int internalpoll(socket_t *socket, polldata_t *data, int events) { + udpsocket_t *udpsocket = (udpsocket_t *)socket; + int revents = 0; + + if (events & POLLIN) { + bool intstate = interrupt_set(false); + spinlock_acquire(&udpsocket->ringbufferlock); + if (RINGBUFFER_DATACOUNT(&udpsocket->ringbuffer) > 0) + revents |= POLLIN; + spinlock_release(&udpsocket->ringbufferlock); + interrupt_set(intstate); + } + + if (events & POLLOUT) + revents |= POLLOUT; + + if (revents == 0 && data) + poll_add(&socket->pollheader, data, events); + + return revents; +} + +static int udp_send(socket_t *socket, sockaddr_t *addr, void *buffer, size_t count, uintmax_t flags, size_t *sendcount) { + udpsocket_t *udpsocket = (udpsocket_t *)socket; + int e; + MUTEX_ACQUIRE(&socket->mutex, false); + + if (addr == NULL && socket->state != SOCKET_STATE_CONNECTED) { + e = ENOTCONN; + goto cleanup; + } + + if ((addr ? addr->ipv4addr.addr : udpsocket->peeraddress) == IPV4_BROADCAST_ADDRESS && (udpsocket->socket.broadcast == 0 || udpsocket->socket.netdev == NULL)) { + e = EACCES; + goto cleanup; + } + + if (socket->state == SOCKET_STATE_UNBOUND) { + uint16_t port = 0; + e = udp_allocport(socket, &port); + udpsocket->port = port; + udpsocket->address = 0; + socket->state = SOCKET_STATE_BOUND; + } + + e = udp_sendpacket(buffer, count, addr ? addr->ipv4addr.addr : udpsocket->peeraddress, udpsocket->port, addr ? addr->ipv4addr.port : udpsocket->peerport, udpsocket->socket.netdev); + + *sendcount = count; + cleanup: + MUTEX_RELEASE(&socket->mutex); + return e; +} + +static int udp_recv(socket_t *socket, sockaddr_t *addr, void *buffer, size_t count, uintmax_t flags, size_t *recvcount) { + udpsocket_t *udpsocket = (udpsocket_t *)socket; + int e = 0; + MUTEX_ACQUIRE(&socket->mutex, false); + + if (udpsocket->port == 0) { + e = EINVAL; + goto cleanup; + } + + for (;;) { + polldesc_t desc = {0}; + e = poll_initdesc(&desc, 1); + if (e) + goto cleanup; + + int revents = internalpoll(socket, &desc.data[0], POLLIN); + + if (revents) + break; + + if (flags & V_FFLAGS_NONBLOCKING) { + e = EAGAIN; + poll_leave(&desc); + poll_destroydesc(&desc); + goto cleanup; + } + + MUTEX_RELEASE(&socket->mutex); + + e = poll_dowait(&desc, 0); + + poll_leave(&desc); + poll_destroydesc(&desc); + + if (e) + return e; + + MUTEX_ACQUIRE(&socket->mutex, false); + } + + bool intstate = interrupt_set(false); + spinlock_acquire(&udpsocket->ringbufferlock); + + dataheader_t header; + __assert(ringbuffer_read(&udpsocket->ringbuffer, &header, sizeof(header)) == sizeof(header)); + + size_t copycount = min(header.length, count); + __assert(ringbuffer_read(&udpsocket->ringbuffer, buffer, copycount) == copycount); + + // truncate the rest if the buffer wasn't big enough to handle everything + if (header.length > count) + ringbuffer_truncate(&udpsocket->ringbuffer, header.length - copycount); + + spinlock_release(&udpsocket->ringbufferlock); + interrupt_set(intstate); + + *recvcount = copycount; + addr->ipv4addr.addr = header.peer; + addr->ipv4addr.port = header.srcport; + + cleanup: + MUTEX_RELEASE(&socket->mutex); + return e; +} + +static void udp_destroy(socket_t *socket) { + // we need to be aware that a packet can still arrive while we are destroying the socket + udpsocket_t *udpsocket = (udpsocket_t *)socket; + if (udpsocket->port) + udp_unallocport(udpsocket->port); + // from now on no new packet will arrive, but another cpu could be processing a packet for this socket + // wait for all inbound packets to be processed + while (__atomic_load_n(&udpsocket->packetsprocessing, __ATOMIC_SEQ_CST) > 0) CPU_PAUSE(); + + // no risk of the socket object being used by udp_process() now + ringbuffer_destroy(&udpsocket->ringbuffer); + free(socket); +} + +static socketops_t socketops = { + .bind = udp_bind, + .send = udp_send, + .recv = udp_recv, + .destroy = udp_destroy +}; + +socket_t *udp_createsocket() { + // XXX possibly move this to a slab? + udpsocket_t *socket = alloc(sizeof(udpsocket_t)); + if (socket == NULL) + return NULL; + + if (ringbuffer_init(&socket->ringbuffer, SOCKET_BUFFER)) { + free(socket); + return NULL; + } + SPINLOCK_INIT(socket->ringbufferlock); + + socket->socket.ops = &socketops; + socket->socket.state = SOCKET_STATE_UNBOUND; + MUTEX_INIT(&socket->socket.mutex); + socket->socket.type = SOCKET_TYPE_UDP; + return (socket_t *)socket; }