summaryrefslogtreecommitdiff
path: root/src/user/app/netstack
diff options
context:
space:
mode:
Diffstat (limited to 'src/user/app/netstack')
-rw-r--r--src/user/app/netstack/arp.c48
-rw-r--r--src/user/app/netstack/ether.c59
-rw-r--r--src/user/app/netstack/fs.c154
-rw-r--r--src/user/app/netstack/icmp.c34
-rw-r--r--src/user/app/netstack/ipv4.c215
-rw-r--r--src/user/app/netstack/netstack.c43
-rw-r--r--src/user/app/netstack/proto.h70
-rw-r--r--src/user/app/netstack/udp.c98
-rw-r--r--src/user/app/netstack/util.c31
-rw-r--r--src/user/app/netstack/util.h38
10 files changed, 790 insertions, 0 deletions
diff --git a/src/user/app/netstack/arp.c b/src/user/app/netstack/arp.c
new file mode 100644
index 0000000..cfb1e04
--- /dev/null
+++ b/src/user/app/netstack/arp.c
@@ -0,0 +1,48 @@
+#include "proto.h"
+#include "util.h"
+#include <string.h>
+
+enum {
+ HdrType = 0,
+ HdrTypeEther = 1,
+ ProtoType = 2,
+ HdrALen = 4,
+ ProtoALen = 5,
+ Operation = 6,
+ OpReq = 1,
+ OpReply = 2,
+};
+
+void arp_parse(const uint8_t *buf, size_t len) {
+ // TODO no bound checks
+ uint16_t htype = nget16(buf + HdrType);
+ uint16_t ptype = nget16(buf + ProtoType);
+ uint16_t op = nget16(buf + Operation);
+
+ if (!(htype == HdrTypeEther && ptype == ET_IPv4)) return;
+ enum { /* only valid for this combination of header + proto */
+ SrcMAC = 8,
+ SrcIP = 14,
+ DstMAC = 18,
+ DstIP = 24,
+ };
+
+ if (op == OpReq) {
+ uint32_t daddr = nget32(buf + DstIP);
+ if (daddr == state.ip) {
+ uint8_t *pkt = ether_start(30, (struct ethernet){
+ .dst = buf + SrcMAC,
+ .type = ET_ARP,
+ });
+ nput16(pkt + HdrType, 1);
+ nput16(pkt + ProtoType, ET_IPv4);
+ pkt[HdrALen] = 6;
+ pkt[ProtoALen] = 4;
+ nput16(pkt + Operation, OpReply);
+ memcpy(pkt + SrcMAC, state.mac, 6);
+ nput32(pkt + SrcIP, state.ip);
+ memcpy(pkt + DstMAC, buf + SrcMAC, 10); /* sender's MAC and IP */
+ ether_finish(pkt);
+ }
+ }
+}
diff --git a/src/user/app/netstack/ether.c b/src/user/app/netstack/ether.c
new file mode 100644
index 0000000..5893632
--- /dev/null
+++ b/src/user/app/netstack/ether.c
@@ -0,0 +1,59 @@
+#include <camellia/syscalls.h>
+#include "proto.h"
+#include "util.h"
+
+enum {
+ DstMAC = 0,
+ SrcMAC = 6,
+ EtherType = 12,
+ Payload = 14,
+};
+struct ethq *ether_queue;
+
+void ether_parse(const uint8_t *buf, size_t len) {
+ struct ethernet ether = (struct ethernet){
+ .src = buf + SrcMAC,
+ .dst = buf + DstMAC,
+ .type = nget16(buf + EtherType),
+ };
+
+ for (struct ethq **iter = &ether_queue; iter && *iter; ) {
+ struct ethq *qe = *iter;
+ _syscall_fs_respond(qe->h, buf, len, 0);
+ /* remove entry */
+ /* yes, doing it this way here doesn't make sense. i'm preparing for filtering */
+ *iter = qe->next;
+ free(qe);
+ }
+
+ switch (ether.type) {
+ case ET_IPv4:
+ ipv4_parse(buf + Payload, len - Payload, ether);
+ break;
+ case ET_ARP:
+ arp_parse(buf + Payload, len - Payload);
+ break;
+ }
+}
+
+static const size_t fhoff = sizeof(size_t);
+uint8_t *ether_start(size_t len, struct ethernet ether) {
+ if (len < 60 - Payload) len = 60 - Payload;
+
+ if (!ether.dst) eprintf("NULL ether.dst!"); // TODO arp? i guess?
+ if (!ether.src) ether.src = &state.mac;
+
+ uint8_t *buf = malloc(fhoff + Payload + len);
+ memset(buf, 0, fhoff + Payload + len);
+ *(size_t*)buf = len + Payload;
+ memcpy(buf + fhoff + DstMAC, ether.dst, 6);
+ memcpy(buf + fhoff + SrcMAC, ether.src, 6);
+ nput16(buf + fhoff + EtherType, ether.type);
+ return buf + fhoff + Payload;
+}
+void ether_finish(uint8_t *pkt) {
+ uint8_t *buf = pkt - Payload - fhoff;
+ size_t len = *(size_t*)buf;
+ _syscall_write(state.raw_h, buf + fhoff, len, 0, 0);
+ free(buf);
+}
diff --git a/src/user/app/netstack/fs.c b/src/user/app/netstack/fs.c
new file mode 100644
index 0000000..b8c2f7b
--- /dev/null
+++ b/src/user/app/netstack/fs.c
@@ -0,0 +1,154 @@
+#include "proto.h"
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <user/lib/fs/dir.h>
+
+enum handle_type {
+ H_ROOT,
+ H_ETHER,
+ H_UDP,
+};
+
+struct strqueue {
+ struct strqueue *next;
+ size_t len;
+ char buf[];
+};
+
+struct handle {
+ enum handle_type type;
+ struct {
+ struct udp_conn *c;
+ struct strqueue *rx, *rxlast;
+ } udp;
+ handle_t reqh;
+};
+
+
+static void udp_listen_callback(struct udp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->udp.c = c;
+ h->udp.rx = NULL;
+ h->udp.rxlast = NULL;
+ _syscall_fs_respond(h->reqh, h, 0, 0);
+ h->reqh = -1;
+}
+
+static void udp_recv_callback(const void *buf, size_t len, void *arg) {
+ struct handle *h = arg;
+ if (h->reqh >= 0) {
+ _syscall_fs_respond(h->reqh, buf, len, 0);
+ h->reqh = -1;
+ return;
+ }
+ // TODO don't malloc on the network thread, dumbass
+ struct strqueue *sq = malloc(sizeof(*sq) + len);
+ sq->next = NULL;
+ sq->len = len;
+ memcpy(sq->buf, buf, len);
+ if (h->udp.rx) {
+ h->udp.rxlast->next = sq;
+ h->udp.rxlast = sq;
+ } else {
+ h->udp.rx = sq;
+ h->udp.rxlast = sq;
+ }
+}
+
+static void udp_recv_enqueue(struct handle *h, handle_t reqh) {
+ if (h->reqh > 0) {
+ // TODO queue
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ } else if (h->udp.rx) {
+ _syscall_fs_respond(reqh, h->udp.rx->buf, h->udp.rx->len, 0);
+ h->udp.rx = h->udp.rx->next;
+ free(h->udp.rx);
+ } else {
+ h->reqh = reqh;
+ }
+}
+
+void fs_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ struct fs_wait_response res;
+ handle_t reqh = _syscall_fs_wait(buf, buflen, &res);
+ if (reqh < 0) break;
+ struct handle *h = res.id;
+ long ret;
+ switch (res.op) {
+ case VFSOP_OPEN:
+ if (res.len == 1 && !memcmp("/", buf, 1)) {
+ h = malloc(sizeof *h);
+ h->type = H_ROOT;
+ _syscall_fs_respond(reqh, h, 0, 0);
+ } else if (res.len == 4 && !memcmp("/raw", buf, 4)) {
+ h = malloc(sizeof *h);
+ h->type = H_ETHER;
+ _syscall_fs_respond(reqh, h, 0, 0);
+ } else if (res.len > 6 && !memcmp("/udpl/", buf, 6)) {
+ uint16_t port = strtol(buf + 6, NULL, 0);
+ h = malloc(sizeof *h);
+ h->type = H_UDP;
+ h->udp.c = NULL;
+ h->reqh = reqh;
+ udp_listen(port, udp_listen_callback, udp_recv_callback, h);
+ } else {
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_READ:
+ switch (h->type) {
+ case H_ROOT: {
+ struct dirbuild db;
+ dir_start(&db, res.offset, buf, sizeof buf);
+ dir_append(&db, "raw");
+ dir_append(&db, "udpl/");
+ _syscall_fs_respond(reqh, buf, dir_finish(&db), 0);
+ break;}
+ case H_ETHER: {
+ struct ethq *qe;
+ qe = malloc(sizeof *qe);
+ qe->h = reqh;
+ qe->next = ether_queue;
+ ether_queue = qe;
+ break;}
+ case H_UDP:
+ udp_recv_enqueue(h, reqh);
+ break;
+ default:
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_WRITE:
+ switch (h->type) {
+ case H_ETHER:
+ ret = _syscall_write(state.raw_h, buf, res.len, 0, 0);
+ _syscall_fs_respond(reqh, NULL, ret, 0);
+ break;
+ case H_UDP:
+ udpc_send(h->udp.c, buf, res.len);
+ _syscall_fs_respond(reqh, NULL, res.len, 0);
+ break;
+ default:
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_CLOSE:
+ // TODO remove entries in queue
+ // TODO why does close even have _syscall_fs_respond?
+ if (h->type == H_UDP)
+ udpc_close(h->udp.c);
+ free(h);
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ break;
+ default:
+ _syscall_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ free(buf);
+}
diff --git a/src/user/app/netstack/icmp.c b/src/user/app/netstack/icmp.c
new file mode 100644
index 0000000..0c6a502
--- /dev/null
+++ b/src/user/app/netstack/icmp.c
@@ -0,0 +1,34 @@
+#include "proto.h"
+#include "util.h"
+
+enum {
+ Type = 0,
+ Code = 1,
+ Checksum = 2,
+ Payload = 4,
+};
+
+void icmp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ if (len < Payload) return;
+ uint8_t type = buf[Type];
+ if (type == 8 && ip.dst == state.ip) {
+ /* echo reply */
+ icmp_send(buf + Payload, len - Payload, (struct icmp){
+ .type = 0,
+ .ip.dst = ip.src,
+ .ip.e.dst = ip.e.src,
+ });
+ }
+}
+
+void icmp_send(const void *payload, size_t len, struct icmp i) {
+ i.ip.proto = 1;
+ uint8_t *pkt = malloc(Payload + len);
+ pkt[Type] = i.type;
+ pkt[Code] = i.code;
+ memcpy(pkt + Payload, payload, len);
+ nput16(pkt + Checksum, 0);
+ nput16(pkt + Checksum, ip_checksum(pkt, Payload + len));
+ ipv4_send(pkt, Payload + len, i.ip);
+ free(pkt);
+}
diff --git a/src/user/app/netstack/ipv4.c b/src/user/app/netstack/ipv4.c
new file mode 100644
index 0000000..3243d3c
--- /dev/null
+++ b/src/user/app/netstack/ipv4.c
@@ -0,0 +1,215 @@
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <stdlib.h>
+
+enum {
+ Version = 0,
+ HdrLen = 0,
+ TotalLen = 2,
+ Id = 4,
+ FragInfo = 6,
+ EvilBit = 0x8000,
+ DontFrag = 0x4000,
+ MoreFrags = 0x2000,
+ FragOff = 0x1FFF,
+ TTL = 8,
+ Proto = 9,
+ Checksum = 10,
+ SrcIP = 12,
+ DstIP = 16,
+};
+static void ipv4_dispatch(const uint8_t *buf, size_t len, struct ipv4 ip);
+
+struct fragment {
+ struct fragment *next; /* sorted */
+ size_t len, offset;
+ bool last;
+ uint8_t buf[];
+};
+struct fragmented {
+ /* src, dst, proto, id come from the first arrived packet
+ * and are used to tell fragmenteds apart.
+ * the rest comes from the sequentially first packet.
+ * ip.h.header points to a malloc'd buffer*/
+ struct ipv4 h;
+
+ struct fragment *first;
+ struct fragmented *next, **prev; /* *(inc->prev) == inc */
+ // TODO timer
+};
+struct fragmented *fragmenteds;
+static struct fragmented *fragmented_find(struct ipv4 fraghdr);
+static void fragmented_tryinsert(const uint8_t *payload, size_t plen, struct ipv4 ip);
+static void fragmented_tryfinish(struct fragmented *inc);
+static void fragmented_free(struct fragmented *inc);
+
+static struct fragmented *fragmented_find(struct ipv4 fraghdr) {
+ struct fragmented *inc;
+ for (inc = fragmenteds; inc; inc = inc->next) {
+ if (inc->h.src == fraghdr.src &&
+ inc->h.dst == fraghdr.dst &&
+ inc->h.proto == fraghdr.proto &&
+ inc->h.id == fraghdr.id)
+ {
+ return inc;
+ }
+ }
+ inc = malloc(sizeof *inc);
+ memset(inc, 0, sizeof *inc);
+ inc->h.src = fraghdr.src;
+ inc->h.dst = fraghdr.dst;
+ inc->h.proto = fraghdr.proto;
+ inc->h.id = fraghdr.id;
+
+ inc->next = fragmenteds;
+ if (inc->next) inc->next->prev = &inc->next;
+ inc->prev = &fragmenteds;
+ *inc->prev = inc;
+ return inc;
+}
+
+static void fragmented_tryinsert(const uint8_t *payload, size_t plen, struct ipv4 ip) {
+ struct fragmented *inc = fragmented_find(ip);
+ size_t off = (ip.fraginfo & FragOff) * 8;
+ bool last = !(ip.fraginfo & MoreFrags);
+ // eprintf("fragmented packet, %u + %u, part of 0x%x", off, plen, inc);
+
+ /* find the first fragment at a bigger offset, and insert before it */
+ struct fragment **insert = &inc->first;
+ for (; *insert; insert = &(*insert)->next) {
+ if ((*insert)->offset > off) break;
+ if ((*insert)->offset == off) return; /* duplicate packet */
+ }
+ /* later on: frag->next = *insert;
+ * if we're the last fragment, frag->next must == NULL */
+ if (last && *insert != NULL) return;
+
+ struct fragment *frag = malloc(sizeof(struct fragment) + plen);
+ frag->next = *insert;
+ *insert = frag;
+ frag->len = plen;
+ frag->offset = off;
+ frag->last = last;
+ memcpy(frag->buf, payload, plen);
+
+ if (off == 0) { /* save header */
+ assert(!inc->h.header);
+ void *headercpy = malloc(ip.hlen);
+ memcpy(headercpy, ip.header, ip.hlen);
+ inc->h = ip;
+ inc->h.header = headercpy;
+ }
+
+ fragmented_tryfinish(inc);
+}
+
+static void fragmented_tryfinish(struct fragmented *inc) {
+ if (inc->first->offset != 0) return;
+ for (struct fragment *iter = inc->first; iter; iter = iter->next) {
+ size_t iterend = iter->offset + iter->len;
+ struct fragment *next = iter->next;
+ if (next) {
+ if (iterend < next->offset) return; /* incomplete */
+ if (iterend > next->offset) {
+ fragmented_free(inc);
+ return;
+ }
+ } else if (iter->last) {
+ void *buf = malloc(iterend);
+ for (struct fragment *iter = inc->first; iter; iter = iter->next) {
+ assert(iter->offset + iter->len <= iterend);
+ memcpy(buf + iter->offset, iter->buf, iter->len);
+ }
+ ipv4_dispatch(buf, iterend, inc->h);
+ free(buf);
+ fragmented_free(inc);
+ }
+ }
+}
+
+static void fragmented_free(struct fragmented *inc) {
+ if (inc->next) {
+ inc->next->prev = inc->prev;
+ *inc->next->prev = inc->next;
+ } else {
+ *inc->prev = NULL;
+ }
+
+ for (struct fragment *next, *iter = inc->first; iter; iter = next) {
+ next = iter->next;
+ free(iter);
+ }
+ free((void*)inc->h.header);
+ free(inc);
+}
+
+
+static void ipv4_dispatch(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ switch (ip.proto) {
+ case 0x01: icmp_parse(buf, len, ip); break;
+ case 0x11: udp_parse(buf, len, ip); break;
+ }
+}
+
+void ipv4_parse(const uint8_t *buf, size_t len, struct ethernet ether) {
+ uint8_t version, headerlen;
+ uint16_t totallen;
+
+ version = buf[Version] >> 4;
+ if (version != 4) return;
+ headerlen = (buf[HdrLen] & 0xf) * 4;
+ totallen = nget16(buf + TotalLen);
+ if (totallen < headerlen) return;
+
+ /* ignores checksum. TODO? */
+
+ struct ipv4 ip = (struct ipv4){
+ .e = ether,
+ .src = nget32(buf + SrcIP),
+ .dst = nget32(buf + DstIP),
+ .id = nget16(buf + Id),
+ .fraginfo = nget16(buf + FragInfo),
+ .proto = buf[Proto],
+ .header = buf,
+ .hlen = headerlen,
+ };
+
+ if (ip.fraginfo & ~(EvilBit | DontFrag)) {
+ fragmented_tryinsert(buf + headerlen, totallen - headerlen, ip);
+ } else {
+ if (totallen > len) return;
+ ipv4_dispatch(buf + headerlen, totallen - headerlen, ip);
+ }
+}
+
+static uint16_t next_id = 0;
+void ipv4_send(const void *payload, size_t len, struct ipv4 ip) {
+ const size_t mtu = 1500;
+ const size_t hdrlen = 20;
+
+ ip.e.type = ET_IPv4;
+ if (!ip.src) ip.src = state.ip;
+ if (!ip.e.dst && ip.dst == 0xFFFFFFFF)
+ ip.e.dst = &MAC_BROADCAST;
+
+ uint16_t id = next_id++;
+ for (size_t off = 0, fraglen = mtu - hdrlen; off < len; off += fraglen) {
+ if (fraglen > len - off)
+ fraglen = len - off;
+ bool last = off + fraglen >= len;
+ uint8_t *pkt = ether_start(hdrlen + fraglen, ip.e);
+ pkt[Version] = 0x40;
+ pkt[HdrLen] |= hdrlen / 4;
+ nput16(pkt + TotalLen, hdrlen + fraglen);
+ nput16(pkt + Id, id);
+ nput16(pkt + FragInfo, (off >> 3) | (last ? 0 : MoreFrags));
+ pkt[TTL] = 0xFF;
+ pkt[Proto] = ip.proto;
+ nput32(pkt + SrcIP, ip.src);
+ nput32(pkt + DstIP, ip.dst);
+ nput16(pkt + Checksum, ip_checksum(pkt, hdrlen));
+ memcpy(pkt + hdrlen, payload + off, fraglen);
+ ether_finish(pkt);
+ }
+}
diff --git a/src/user/app/netstack/netstack.c b/src/user/app/netstack/netstack.c
new file mode 100644
index 0000000..0451511
--- /dev/null
+++ b/src/user/app/netstack/netstack.c
@@ -0,0 +1,43 @@
+#include "proto.h"
+#include "util.h"
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <user/lib/thread.h>
+
+struct net_state state = {
+ // TODO dynamically get mac
+ .mac = {0x52, 0x54, 0x00, 0xCA, 0x77, 0x1A},
+ .ip = (192 << 24) + (168 << 16) + 11,
+};
+
+void network_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ long ret = _syscall_read(state.raw_h, buf, buflen, -1);
+ if (ret < 0) break;
+ ether_parse((void*)buf, ret);
+ }
+ free(buf);
+}
+
+void fs_thread(void *arg);
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ eprintf("no argument");
+ return 1;
+ }
+ state.raw_h = _syscall_open(argv[1], strlen(argv[1]), 0);
+ if (state.raw_h < 0) {
+ eprintf("couldn't open %s", argv[1]);
+ return 1;
+ }
+ thread_create(0, network_thread, NULL);
+ thread_create(0, fs_thread, NULL);
+ _syscall_await();
+ return 0;
+}
diff --git a/src/user/app/netstack/proto.h b/src/user/app/netstack/proto.h
new file mode 100644
index 0000000..106f286
--- /dev/null
+++ b/src/user/app/netstack/proto.h
@@ -0,0 +1,70 @@
+#pragma once
+#include <camellia/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef uint8_t mac_t[6];
+static const mac_t MAC_BROADCAST = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+extern struct net_state {
+ mac_t mac;
+ uint32_t ip;
+
+ handle_t raw_h;
+} state;
+
+enum { /* ethertype */
+ ET_IPv4 = 0x800,
+ ET_ARP = 0x806,
+};
+
+struct ethernet {
+ const mac_t *src, *dst;
+ uint16_t type;
+};
+
+struct ipv4 {
+ struct ethernet e;
+ uint32_t src, dst;
+ uint16_t id, fraginfo;
+ uint8_t proto;
+ const uint8_t *header; size_t hlen;
+};
+
+struct icmp {
+ struct ipv4 ip;
+ uint8_t type, code;
+};
+
+
+/* NOT THREADSAFE, YET USED FROM CONCURRENT THREADS
+ * will break if i implement a scheduler*/
+struct ethq {
+ struct ethq *next;
+ handle_t h;
+};
+extern struct ethq *ether_queue;
+
+void arp_parse(const uint8_t *buf, size_t len);
+
+void icmp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+void icmp_send(const void *payload, size_t len, struct icmp i);
+
+void ipv4_parse(const uint8_t *buf, size_t len, struct ethernet ether);
+void ipv4_send(const void *payload, size_t len, struct ipv4 ip);
+
+void ether_parse(const uint8_t *buf, size_t len);
+uint8_t *ether_start(size_t len, struct ethernet ether);
+void ether_finish(uint8_t *pkt);
+
+struct udp_conn;
+void udp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+/* calls callback once, after a client connects. */
+void udp_listen(uint16_t port,
+ void (*on_conn)(struct udp_conn *, void *carg),
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg);
+// TODO udp_onclosed
+void udpc_send(struct udp_conn *, const void *buf, size_t len);
+/* frees */
+void udpc_close(struct udp_conn *);
diff --git a/src/user/app/netstack/udp.c b/src/user/app/netstack/udp.c
new file mode 100644
index 0000000..8129a48
--- /dev/null
+++ b/src/user/app/netstack/udp.c
@@ -0,0 +1,98 @@
+#include "proto.h"
+#include "util.h"
+
+enum {
+ SrcPort = 0,
+ DstPort = 2,
+ Length = 4,
+ Checksum = 6,
+ Payload = 8,
+};
+
+
+struct udp_conn {
+ uint32_t lip, rip;
+ uint16_t lport, rport;
+ mac_t rmac;
+ void (*on_conn)(struct udp_conn *, void *); /* if non-NULL - listening, if NULL - estabilished */
+ void (*on_recv)(const void *, size_t, void *);
+ void *carg;
+ struct udp_conn *next, **prev;
+};
+struct udp_conn *conns;
+void udp_listen(uint16_t port,
+ void (*on_conn)(struct udp_conn *, void *carg),
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg)
+{
+ if (!on_conn) return;
+ struct udp_conn *c = malloc(sizeof *c);
+ c->lport = port;
+ c->lip = state.ip;
+ c->on_conn = on_conn;
+ c->on_recv = on_recv;
+ c->carg = carg;
+
+ c->next = conns;
+ if (c->next) *(c->next->prev) = c;
+ c->prev = &conns;
+ *c->prev = c;
+}
+void udpc_close(struct udp_conn *c) {
+ if (c->next) c->next->prev = c->prev;
+ *(c->prev) = c->next;
+ free(c);
+}
+void udpc_send(struct udp_conn *c, const void *buf, size_t len) {
+ uint8_t *pkt = malloc(Payload + len);
+ nput16(pkt + SrcPort, c->lport);
+ nput16(pkt + DstPort, c->rport);
+ nput16(pkt + Length, Payload + len);
+ nput16(pkt + Checksum, 0);
+ memcpy(pkt + Payload, buf, len);
+ ipv4_send(pkt, Payload + len, (struct ipv4){
+ .proto = 0x11,
+ .src = c->lip,
+ .dst = c->rip,
+ .e.dst = c->rmac,
+ });
+ free(pkt);
+}
+
+
+void udp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ uint16_t srcport = nget16(buf + SrcPort);
+ uint16_t dstport = nget16(buf + DstPort);
+ bool active = false;
+
+ for (struct udp_conn *iter = conns; iter; iter = iter->next) {
+ if (iter->on_conn && dstport == iter->lport) {
+ iter->on_conn(iter, iter->carg);
+ iter->on_conn = NULL;
+ iter->rport = srcport;
+ memcpy(&iter->rmac, ip.e.src, sizeof(mac_t));
+ iter->rip = ip.src;
+ }
+ if (iter->rip == ip.src
+ && iter->rport == srcport
+ && iter->lport == dstport
+ && iter->on_recv)
+ {
+ active = true;
+ iter->on_recv(buf + Payload, len - Payload, iter->carg);
+ }
+ }
+
+ if (!active) {
+ uint8_t *pkt = malloc(4 + ip.hlen + 8);
+ nput32(pkt, 0);
+ memcpy(pkt + 4, ip.header, ip.hlen + 8);
+ icmp_send(pkt, 4 + ip.hlen + 8, (struct icmp){
+ .type = 3, /* destination unreachable */
+ .code = 3, /* port unreachable */
+ .ip.dst = ip.src,
+ .ip.e.dst = ip.e.src,
+ });
+ free(pkt);
+ }
+}
diff --git a/src/user/app/netstack/util.c b/src/user/app/netstack/util.c
new file mode 100644
index 0000000..7c484f3
--- /dev/null
+++ b/src/user/app/netstack/util.c
@@ -0,0 +1,31 @@
+#include "util.h"
+
+/* https://www.w3.org/TR/PNG/#D-CRCAppendix */
+static uint32_t crc_table[256];
+uint32_t crc32(const uint8_t *buf, size_t len) {
+ if (!crc_table[1]) {
+ for (int i = 0; i < 256; i++) {
+ uint32_t c = i;
+ for (int j = 0; j < 8; j++)
+ c = ((c&1) ? 0xedb88320 : 0) ^ (c >> 1);
+ crc_table[i] = c;
+ }
+ }
+
+ uint32_t c = 0xFFFFFFFF;
+ for (size_t i = 0; i < len; i++)
+ c = crc_table[(c ^ buf[i]) & 0xff] ^ (c >> 8);
+ return ~c;
+}
+
+uint16_t ip_checksum(const uint8_t *buf, size_t len) {
+ uint32_t c = 0;
+ while (len >= 2) {
+ c += nget16(buf);
+ buf += 2; len -= 2;
+ }
+ if (len) c += (*buf) << 8;
+ while (c >= 0xFFFF)
+ c = (c & 0xFFFF) + (c >> 16);
+ return ~c;
+}
diff --git a/src/user/app/netstack/util.h b/src/user/app/netstack/util.h
new file mode 100644
index 0000000..5472b05
--- /dev/null
+++ b/src/user/app/netstack/util.h
@@ -0,0 +1,38 @@
+#pragma once
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define eprintf(fmt, ...) fprintf(stderr, "netstack: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+uint32_t crc32(const uint8_t *buf, size_t len);
+uint16_t ip_checksum(const uint8_t *buf, size_t len);
+
+static inline void nput16(void *vbuf, uint16_t n) {
+ uint8_t *b = vbuf;
+ b[0] = n >> 8;
+ b[1] = n >> 0;
+}
+
+static inline void nput32(void *vbuf, uint32_t n) {
+ uint8_t *b = vbuf;
+ b[0] = n >> 24;
+ b[1] = n >> 16;
+ b[2] = n >> 8;
+ b[3] = n >> 0;
+}
+
+static inline uint16_t nget16(const void *vbuf) {
+ const uint8_t *b = vbuf;
+ return (b[0] << 8)
+ | (b[1] << 0);
+}
+
+static inline uint32_t nget32(const void *vbuf) {
+ const uint8_t *b = vbuf;
+ return (b[0] << 24)
+ | (b[1] << 16)
+ | (b[2] << 8)
+ | (b[3] << 0);
+}