diff --git a/kernel-src/io/net/arp.c b/kernel-src/io/net/arp.c new file mode 100644 index 00000000..c53c8a2b --- /dev/null +++ b/kernel-src/io/net/arp.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CACHE_TIMEOUT_SEC 10 +#define CACHE_TIMEOUT_MS (CACHE_TIMEOUT_SEC * 1000) +#define LOOKUP_TIMEOUT_SEC 5 + +typedef struct { + mac_t mac; + timespec_t time; +} entry_t; +typedef struct { + netdev_t *netdev; + uint32_t ip; +} key_t; + +static scache_t *entryallocator; + +static thread_t *handlerthread; +static event_t handlerthreadwakeup; + +static thread_t *cleanupthread; + +static mutex_t cachelock; +static hashtable_t caches; + +static int get(netdev_t *netdev, uint32_t ip, mac_t *mac) { + void *ptr; + int e = hashtable_get(&netdev->arpcache, &ptr, &ip, sizeof(ip)); + entry_t *entry = ptr; + if (e == 0) + *mac = entry->mac; + + return e; +} + +static void set(netdev_t *netdev, uint32_t ip, mac_t mac, timespec_t time) { + // set cache into the cache list if not already there + void *cachetmp; + if (hashtable_get(&caches, &cachetmp, &netdev, sizeof(netdev))) { + // if it can't be set, give up as it would not be able to be cleaned up later + if (hashtable_set(&caches, &netdev->arpcache, &netdev, sizeof(netdev), true)) + return; + } + + mac_t tmp; + // already exists, just signal to the waiting threads + if (get(netdev, ip, &tmp) == 0) { + return; + } + + entry_t *entry = slab_allocate(entryallocator); + // the thread waiting for the lookup will just timeout if the system is out of memory. not ideal but meh + if (entry == NULL) + return; + + entry->mac = mac; + entry->time = time; + + // same here, waiting thread will just timeout if set errors out + if (hashtable_set(&netdev->arpcache, entry, &ip, sizeof(ip), true)) { + slab_free(entryallocator, entry); + return; + } +} + +static void cleanupthreadfn() { + for (;;) { + sched_sleepus(1000000); // 1 second + MUTEX_ACQUIRE(&cachelock, false); + timespec_t currtime = timekeeper_time(); + hashentry_t entrybuffer; + HASHTABLE_FOREACH(&caches) { + hashtable_t *arpcache = entry->value; + + HASHTABLE_FOREACH(arpcache) { + entry_t *cacheentry = entry->value; + if (timespec_diffms(currtime, cacheentry->time) < CACHE_TIMEOUT_MS) + continue; + + // swap the current entry pointer (that is in the hashtable) to a local copy so we can keep the next pointer + // without breaking the loop by using released memory + // hacky solution but :P + entrybuffer = *entry; + entry = &entrybuffer; + hashtable_remove(arpcache, entry->key, entry->keysize); + slab_free(entryallocator, cacheentry); + } + } + MUTEX_RELEASE(&cachelock); + } +} + +typedef struct { + uint16_t hwtype; + uint16_t protocoltype; + uint8_t hwlen; + uint8_t ptlen; + uint16_t opcode; + mac_t srchw; + uint32_t srcpt; + mac_t dsthw; + uint32_t dstpt; +} __attribute__((packed)) arpframe_t; + +static ringbuffer_t processbuffer; +static semaphore_t psem; +static spinlock_t ringlock; + +typedef struct { + netdev_t *netdev; + uint32_t ip; + mac_t mac; +} bufferentry_t; + +static void handlerthreadfn() { + for (;;) { + semaphore_wait(&psem, false); + bufferentry_t entry; + int old = interrupt_raiseipl(IPL_DPC); + spinlock_acquire(&ringlock); + + ringbuffer_read(&processbuffer, &entry, sizeof(entry)); + + spinlock_release(&ringlock); + interrupt_loweripl(old); + + MUTEX_ACQUIRE(&cachelock, false); + timespec_t time = timekeeper_time(); + set(entry.netdev, entry.ip, entry.mac, time); + MUTEX_RELEASE(&cachelock); + + } +} + +// executed in dpc context +void arp_process(netdev_t *netdev, void *buffer) { + arpframe_t *frame = buffer; + __assert(frame->hwtype == cpu_to_be_w(1)); // ethernet + __assert(frame->protocoltype == cpu_to_be_w(0x800)); // ip + __assert(frame->hwlen == 6); + __assert(frame->ptlen == 4); + __assert(frame->opcode == cpu_to_be_w(2)); + __assert(MAC_EQUAL(&frame->dsthw, &netdev->mac)); + + bufferentry_t entry = { + .netdev = netdev, + .ip = be_to_cpu_d(frame->srcpt), + .mac = frame->srchw + }; + + spinlock_acquire(&ringlock); + ringbuffer_write(&processbuffer, &entry, sizeof(entry)); + spinlock_release(&ringlock); + semaphore_signal(&psem); +} + +static int sendrequest(netdev_t *netdev, uint32_t ip) { + arpframe_t frame = { + .hwtype = cpu_to_be_w(1), + .protocoltype = cpu_to_be_w(0x800), + .hwlen = 6, + .ptlen = 4, + .opcode = cpu_to_be_w(1), + .srcpt = 0, + .dsthw = (mac_t){{0,0,0,0,0,0}}, + .dstpt = cpu_to_be_d(ip) + }; + + memcpy(&frame.srchw, &netdev->mac, sizeof(mac_t)); + netdesc_t desc; + int e = netdev->allocdesc(sizeof(arpframe_t), &desc); + if (e) + return e; + + memcpy((void *)((uintptr_t)desc.address + desc.curroffset), &frame, sizeof(frame)); + + e = netdev->sendpacket(netdev, desc, NET_BROADCAST_MAC, ETH_PROTO_ARP); + // TODO free netdesc + return e; +} + +int arp_lookup(netdev_t *netdev, uint32_t ip, mac_t *mac) { + MUTEX_ACQUIRE(&cachelock, false); + int e = 0; + if (get(netdev, ip, mac) == 0) + goto cleanup; + + // not found, request it + e = sendrequest(netdev, ip); + if (e) + goto cleanup; + + + // wait + // TODO wait on an event instead of looping on a yield (ok for now, but might cause slowdows in the future) + + timespec_t end = timekeeper_time(); + //end.s += LOOKUP_TIMEOUT_SEC; + timespec_t other; + other.s = 0; + other.ns = 100000000; + end = timespec_add(end, other); + + for (;;) { + MUTEX_RELEASE(&cachelock); + sched_yield(); + MUTEX_ACQUIRE(&cachelock, true); + + timespec_t time = timekeeper_time(); + if (get(netdev, ip, mac) == 0) { + e = 0; + break; + } + + if (time.s > end.s || (end.s == time.s && time.ns >= end.ns)) { + e = ENETUNREACH; + break; + } + } + MUTEX_RELEASE(&cachelock); + + cleanup: + return e; +} + +void arp_init() { + ringbuffer_init(&processbuffer, sizeof(bufferentry_t) * 1000); + + entryallocator = slab_newcache(sizeof(entry_t), 0, NULL, NULL); + __assert(entryallocator); + + handlerthread = sched_newthread(handlerthreadfn, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(handlerthread); + cleanupthread = sched_newthread(cleanupthreadfn, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(cleanupthread); + + EVENT_INIT(&handlerthreadwakeup); + + MUTEX_INIT(&cachelock); + + SEMAPHORE_INIT(&psem, 0); + + SPINLOCK_INIT(ringlock); + + __assert(hashtable_init(&caches, 10) == 0); + + sched_queue(handlerthread); + sched_queue(cleanupthread); +}