summaryrefslogtreecommitdiff
path: root/src/cmd/netstack
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/netstack')
-rw-r--r--src/cmd/netstack/arp.c151
-rw-r--r--src/cmd/netstack/ether.c59
-rw-r--r--src/cmd/netstack/fs.c320
-rw-r--r--src/cmd/netstack/icmp.c34
-rw-r--r--src/cmd/netstack/ipv4.c216
-rw-r--r--src/cmd/netstack/netstack.c53
-rw-r--r--src/cmd/netstack/proto.h107
-rw-r--r--src/cmd/netstack/tcp.c268
-rw-r--r--src/cmd/netstack/udp.c124
-rw-r--r--src/cmd/netstack/util.c60
-rw-r--r--src/cmd/netstack/util.h44
11 files changed, 1436 insertions, 0 deletions
diff --git a/src/cmd/netstack/arp.c b/src/cmd/netstack/arp.c
new file mode 100644
index 0000000..3a1c8da
--- /dev/null
+++ b/src/cmd/netstack/arp.c
@@ -0,0 +1,151 @@
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+
+enum {
+ HdrType = 0,
+ HdrTypeEther = 1,
+ ProtoType = 2,
+ HdrALen = 4,
+ ProtoALen = 5,
+ Operation = 6,
+ OpReq = 1,
+ OpReply = 2,
+};
+
+struct arpc {
+ struct arpc *next;
+ uint32_t ip;
+ mac_t mac;
+};
+static struct arpc *arpcache;
+static void arpcache_put(uint32_t ip, mac_t mac);
+
+void arp_parse(const uint8_t *buf, size_t len) {
+ if (len < Operation + 2) return;
+ 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 (len < DstIP + 4) return;
+ arpcache_put(nget32(buf + SrcIP), *(mac_t*)buf + SrcMAC);
+
+ if (op == OpReq) {
+ uint32_t daddr = nget32(buf + DstIP);
+ if (daddr == state.ip) {
+ uint8_t *pkt = ether_start(30, (struct ethernet){
+ .dst = (void*)(buf + SrcMAC),
+ .type = ET_ARP,
+ });
+ nput16(pkt + HdrType, HdrTypeEther);
+ 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);
+ }
+ }
+}
+
+void arp_request(uint32_t ip) {
+ enum {
+ SrcMAC = 8,
+ SrcIP = 14,
+ DstMAC = 18,
+ DstIP = 24,
+ };
+ uint8_t *pkt = ether_start(28, (struct ethernet){
+ .src = &state.mac,
+ .dst = &MAC_BROADCAST,
+ .type = ET_ARP,
+ });
+ nput16(pkt + HdrType, HdrTypeEther);
+ nput16(pkt + ProtoType, ET_IPv4);
+ pkt[HdrALen] = 6;
+ pkt[ProtoALen] = 4;
+ nput16(pkt + Operation, OpReq);
+ memcpy(pkt + SrcMAC, state.mac, 6);
+ nput32(pkt + SrcIP, state.ip);
+ memcpy(pkt + DstMAC, &MAC_BROADCAST, 6);
+ nput32(pkt + DstIP, ip);
+ ether_finish(pkt);
+}
+
+static void arpcache_put(uint32_t ip, mac_t mac) {
+ for (struct arpc *iter = arpcache; iter; iter = iter->next) {
+ if (memcmp(iter->mac, mac, 6) == 0) {
+ if (iter->ip == ip) return; /* cache entry correct */
+ else break; /* cache entry needs updating */
+ }
+ }
+ struct arpc *e = malloc(sizeof *e);
+ e->next = arpcache;
+ e->ip = ip;
+ memcpy(e->mac, mac, 6);
+ arpcache = e;
+}
+
+int arpcache_get(uint32_t ip, mac_t *mac) {
+ for (struct arpc *iter = arpcache; iter; iter = iter->next) {
+ if (iter->ip == ip) {
+ if (mac) memcpy(mac, iter->mac, 6);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void arp_fsread(hid_t h, long offset) {
+ const char *fmt = "%08x\t%02x:%02x:%02x:%02x:%02x:%02x\n";
+ long linelen = snprintf(NULL, 0, fmt, 0, 1, 2, 3, 4, 5, 6) + 1;
+ char buf[28];
+ assert(linelen <= (long)sizeof(buf));
+ if (offset < 0) goto err;
+
+ struct arpc *cur = arpcache;
+ if (!cur) goto err;
+ for (; linelen <= offset; offset -= linelen) {
+ cur = cur->next;
+ if (!cur) goto err;
+ }
+ assert(0 <= offset && offset < linelen);
+
+ snprintf(buf, sizeof buf, fmt, cur->ip,
+ cur->mac[0],
+ cur->mac[1],
+ cur->mac[2],
+ cur->mac[3],
+ cur->mac[4],
+ cur->mac[5]);
+ _sys_fs_respond(h, buf + offset, linelen - offset, 0);
+ return;
+err:
+ _sys_fs_respond(h, NULL, -1, 0);
+}
+
+long arp_fswrite(const char *buf, long len, long offset) {
+ if (offset != -1) return -1;
+ uint32_t ip;
+ char tmp[16];
+ size_t iplen = len < 15 ? len : 15;
+ memcpy(tmp, buf, iplen);
+ tmp[iplen] = '\0';
+ if (ip_parse(tmp, &ip) < 0) {
+ return -1;
+ } else {
+ arp_request(ip);
+ return len;
+ }
+}
diff --git a/src/cmd/netstack/ether.c b/src/cmd/netstack/ether.c
new file mode 100644
index 0000000..52abac2
--- /dev/null
+++ b/src/cmd/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 = (void*)(buf + SrcMAC),
+ .dst = (void*)(buf + DstMAC),
+ .type = nget16(buf + EtherType),
+ };
+
+ for (struct ethq **iter = &ether_queue; iter && *iter; ) {
+ struct ethq *qe = *iter;
+ _sys_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;
+ _sys_write(state.raw_h, buf + fhoff, len, 0, 0);
+ free(buf);
+}
diff --git a/src/cmd/netstack/fs.c b/src/cmd/netstack/fs.c
new file mode 100644
index 0000000..6d51c35
--- /dev/null
+++ b/src/cmd/netstack/fs.c
@@ -0,0 +1,320 @@
+/*
+ * path format:
+ * /net/raw
+ * raw ethernet frames (read-write)
+ * /net/arp
+ * ARP cache (currently read-only)
+ * /net/connect/0.0.0.0/1.2.3.4/udp/53
+ * connect from 0.0.0.0 (any ip) to 1.2.3.4 on udp port 53
+ * /net/listen/0.0.0.0/{tcp,udp}/53
+ * waits for a connection to any ip on udp port 53
+ * open() returns once a connection to ip 0.0.0.0 on udp port 53 is received
+ */
+#include "proto.h"
+#include "util.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum handle_type {
+ H_ETHER,
+ H_TCP,
+ H_UDP,
+ H_ARP,
+};
+
+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;
+ struct {
+ struct tcp_conn *c;
+ size_t readcap;
+ } tcp;
+ bool dead;
+ hid_t reqh;
+};
+
+
+static void tcp_listen_callback(struct tcp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->tcp.c = c;
+ _sys_fs_respond(h->reqh, h, 0, 0);
+ h->reqh = -1;
+}
+
+/* also called from recv_enqueue. yes, it's a mess */
+static void tcp_recv_callback(void *arg) {
+ struct handle *h = arg;
+ char buf[1024];
+ if (h->reqh >= 0) {
+ if (h->tcp.readcap > sizeof buf)
+ h->tcp.readcap = sizeof buf;
+ size_t len = tcpc_tryread(h->tcp.c, buf, h->tcp.readcap);
+ if (len > 0) {
+ _sys_fs_respond(h->reqh, buf, len, 0);
+ h->reqh = -1;
+ }
+ }
+}
+
+static void tcp_close_callback(void *arg) {
+ struct handle *h = arg;
+ h->dead = true;
+ if (h->reqh >= 0) {
+ _sys_fs_respond(h->reqh, NULL, -ECONNRESET, 0);
+ h->reqh = -1;
+ return;
+ }
+}
+
+static void udp_listen_callback(struct udp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->udp.c = c;
+ _sys_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) {
+ _sys_fs_respond(h->reqh, buf, len, 0);
+ h->reqh = -1;
+ return;
+ }
+ 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 recv_enqueue(struct handle *h, hid_t reqh, size_t readcap) {
+ if (h->reqh > 0) {
+ // TODO queue
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ return;
+ }
+ if (h->type == H_UDP && h->udp.rx) {
+ _sys_fs_respond(reqh, h->udp.rx->buf, h->udp.rx->len, 0);
+ h->udp.rx = h->udp.rx->next;
+ free(h->udp.rx);
+ return;
+ }
+ h->reqh = reqh;
+ if (h->type == H_TCP) {
+ h->tcp.readcap = readcap;
+ tcp_recv_callback(h);
+ }
+}
+
+static void fs_open(hid_t reqh, char *path, int flags) {
+#define respond(buf, val) do{ _sys_fs_respond(reqh, buf, val, 0); return; }while(0)
+ struct handle *h;
+ if (*path != '/') respond(NULL, -1);
+ path++;
+
+ if (strcmp(path, "raw") == 0) {
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_ETHER;
+ respond(h, 0);
+ } else if (strcmp(path, "arp") == 0) {
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_ARP;
+ respond(h, 0);
+ }
+
+ /* everything below ends up sending packets */
+ if (!OPEN_WRITEABLE(flags))
+ respond(NULL, -EACCES);
+
+ char *save;
+ const char *verb, *proto, *port_s;
+ uint32_t srcip, dstip;
+
+ verb = strtok_r(path, "/", &save);
+ if (!verb) respond(NULL, -1);
+
+ if (ip_parse(strtok_r(NULL, "/", &save), &srcip) < 0)
+ respond(NULL, -1);
+ if (srcip != 0) {
+ eprintf("unimplemented");
+ respond(NULL, -1);
+ }
+
+ if (strcmp(verb, "listen") == 0) {
+ proto = strtok_r(NULL, "/", &save);
+ if (!proto) respond(NULL, -1);
+ if (strcmp(proto, "udp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_UDP;
+ h->reqh = reqh;
+ udp_listen(port, udp_listen_callback, udp_recv_callback, h);
+ return;
+ }
+ }
+ if (strcmp(proto, "tcp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_TCP;
+ h->reqh = reqh;
+ tcp_listen(port, tcp_listen_callback, tcp_recv_callback, tcp_close_callback, h);
+ return;
+ }
+ }
+ } else if (strcmp(verb, "connect") == 0) {
+ if (ip_parse(strtok_r(NULL, "/", &save), &dstip) < 0)
+ respond(NULL, -1);
+ proto = strtok_r(NULL, "/", &save);
+ if (!proto) respond(NULL, -1);
+ if (strcmp(proto, "tcp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_TCP;
+ h->tcp.c = tcpc_new((struct tcp){
+ .dst = port,
+ .ip.dst = dstip,
+ }, tcp_recv_callback, tcp_close_callback, h);
+ if (h->tcp.c) {
+ respond(h, 0);
+ } else {
+ free(h);
+ respond(NULL, -1);
+ }
+ }
+ }
+ if (strcmp(proto, "udp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_UDP;
+ h->udp.c = udpc_new((struct udp){
+ .dst = port,
+ .ip.dst = dstip,
+ }, udp_recv_callback, h);
+ if (h->udp.c) {
+ respond(h, 0);
+ } else {
+ free(h);
+ respond(NULL, -1);
+ }
+ }
+ }
+ }
+ respond(NULL, -1);
+#undef respond
+}
+
+void fs_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ struct ufs_request res;
+ hid_t reqh = _sys_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 < buflen) {
+ buf[res.len] = '\0';
+ fs_open(reqh, buf, res.flags);
+ } else {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_READ:
+ if (h->dead) {
+ _sys_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
+ switch (h->type) {
+ case H_ETHER: {
+ struct ethq *qe;
+ qe = malloc(sizeof *qe);
+ qe->h = reqh;
+ qe->next = ether_queue;
+ ether_queue = qe;
+ break;}
+ case H_TCP:
+ case H_UDP:
+ recv_enqueue(h, reqh, res.capacity);
+ break;
+ case H_ARP:
+ arp_fsread(reqh, res.offset);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_WRITE:
+ if (h->dead) {
+ _sys_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
+ switch (h->type) {
+ case H_ETHER:
+ ret = _sys_write(state.raw_h, buf, res.len, 0, 0);
+ _sys_fs_respond(reqh, NULL, ret, 0);
+ break;
+ case H_TCP:
+ tcpc_send(h->tcp.c, buf, res.len);
+ _sys_fs_respond(reqh, NULL, res.len, 0);
+ break;
+ case H_UDP:
+ udpc_send(h->udp.c, buf, res.len);
+ _sys_fs_respond(reqh, NULL, res.len, 0);
+ break;
+ case H_ARP:
+ _sys_fs_respond(reqh, NULL, arp_fswrite(buf, res.len, res.offset), 0);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_CLOSE:
+ // TODO remove entries in queue
+ // TODO why does close even have _sys_fs_respond?
+ if (h->type == H_TCP) tcpc_close(h->tcp.c);
+ if (h->type == H_UDP) udpc_close(h->udp.c);
+ free(h);
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ free(buf);
+}
diff --git a/src/cmd/netstack/icmp.c b/src/cmd/netstack/icmp.c
new file mode 100644
index 0000000..0c6a502
--- /dev/null
+++ b/src/cmd/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/cmd/netstack/ipv4.c b/src/cmd/netstack/ipv4.c
new file mode 100644
index 0000000..1336dc1
--- /dev/null
+++ b/src/cmd/netstack/ipv4.c
@@ -0,0 +1,216 @@
+#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 0x06: tcp_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/cmd/netstack/netstack.c b/src/cmd/netstack/netstack.c
new file mode 100644
index 0000000..2636429
--- /dev/null
+++ b/src/cmd/netstack/netstack.c
@@ -0,0 +1,53 @@
+#include "proto.h"
+#include "util.h"
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <thread.h>
+
+struct net_state state = {
+ // TODO dynamically get mac
+ .mac = {0x52, 0x54, 0x00, 0xCA, 0x77, 0x1A},
+};
+
+void network_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ long ret = _sys_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 < 4) {
+ eprintf("usage: netstack iface ip gateway");
+ return 1;
+ }
+ state.raw_h = camellia_open(argv[1], OPEN_RW);
+ if (state.raw_h < 0) {
+ eprintf("couldn't open %s", argv[1]);
+ return 1;
+ }
+ if (ip_parse(argv[2], &state.ip) < 0) {
+ eprintf("invalid ip");
+ return -1;
+ }
+ if (ip_parse(argv[3], &state.gateway) < 0) {
+ eprintf("invalid gateway");
+ return -1;
+ }
+ setproctitle(argv[2]);
+ arp_request(state.gateway);
+ thread_create(0, network_thread, NULL);
+ thread_create(0, fs_thread, NULL);
+ _sys_await();
+ return 0;
+}
diff --git a/src/cmd/netstack/proto.h b/src/cmd/netstack/proto.h
new file mode 100644
index 0000000..8ea11ac
--- /dev/null
+++ b/src/cmd/netstack/proto.h
@@ -0,0 +1,107 @@
+#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, gateway;
+
+ hid_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 tcp {
+ struct ipv4 ip;
+ uint16_t src, dst;
+};
+
+struct udp {
+ struct ipv4 ip;
+ uint16_t src, dst;
+};
+
+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;
+ hid_t h;
+};
+extern struct ethq *ether_queue;
+
+void arp_parse(const uint8_t *buf, size_t len);
+void arp_request(uint32_t ip);
+/* 0 on success, -1 on failure */
+int arpcache_get(uint32_t ip, mac_t *mac);
+void arp_fsread(hid_t h, long offset);
+long arp_fswrite(const char *buf, long len, long offset);
+
+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);
+struct udp_conn *udpc_new(
+ struct udp u,
+ 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 *);
+
+struct tcp_conn;
+void tcp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+void tcp_listen(
+ uint16_t port,
+ void (*on_conn)(struct tcp_conn *, void *carg),
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg);
+struct tcp_conn *tcpc_new(
+ struct tcp t,
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg);
+size_t tcpc_tryread(struct tcp_conn *, void *buf, size_t len);
+void tcpc_send(struct tcp_conn *, const void *buf, size_t len);
+void tcpc_close(struct tcp_conn *);
diff --git a/src/cmd/netstack/tcp.c b/src/cmd/netstack/tcp.c
new file mode 100644
index 0000000..d1adeab
--- /dev/null
+++ b/src/cmd/netstack/tcp.c
@@ -0,0 +1,268 @@
+/* Welcome to spaghetti land.
+ * This is anything but production quality. It's throwaway code, meant
+ * only to see how networking could fit into the architecture of the
+ * system. */
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <shared/ring.h>
+
+enum {
+ SrcPort = 0,
+ DstPort = 2,
+ Seq = 4,
+ AckNum = 8, /* last processed seq + 1 */
+ Flags = 12,
+ FlagSize = 0xF000, /* size of header / 4 bytes */
+ /* 3 bits: 0, still unused
+ * 3 bits: modern stuff not in the original RFC */
+ FlagURG = 0x0020, /* urgent */
+ FlagACK = 0x0010,
+ FlagPSH = 0x0008, /* force buffer flush */
+ FlagRST = 0x0004, /* reset connection */
+ FlagSYN = 0x0002, /* first packet; sync sequence numbers */
+ FlagFIN = 0x0001, /* last packet */
+ FlagAll = 0x003F,
+ WinSize = 14, /* amount of data we're willing to receive */
+ Checksum = 16,
+ Urgent = 18,
+ MinHdr = 20,
+};
+
+enum tcp_state {
+ LISTEN,
+ SYN_SENT,
+ SYN_RCVD,
+ ESTABILISHED,
+ LAST_ACK,
+ CLOSED,
+};
+
+struct tcp_conn {
+ uint32_t lip, rip;
+ mac_t rmac;
+ uint16_t lport, rport;
+ struct tcp_conn *next, **link;
+
+ enum tcp_state state;
+ uint32_t lack, rack;
+ uint32_t lseq;
+ bool uclosed; /* did the user close? */
+
+ void (*on_conn)(struct tcp_conn *, void *carg);
+ void (*on_recv)(void *carg);
+ void (*on_close)(void *carg);
+ void *carg;
+
+ ring_t rx;
+};
+static struct tcp_conn *conns;
+static void conns_append(struct tcp_conn *c) {
+ c->next = conns;
+ if (c->next)
+ c->next->link = &c->next;
+ c->link = &conns;
+ *c->link = c;
+}
+static void tcpc_sendraw(struct tcp_conn *c, uint16_t flags, const void *buf, size_t len) {
+ uint8_t *pkt = malloc(MinHdr + len);
+ memset(pkt, 0, MinHdr);
+
+ nput16(pkt + SrcPort, c->lport);
+ nput16(pkt + DstPort, c->rport);
+ nput32(pkt + Seq, c->lseq);
+ c->lseq += len;
+ nput32(pkt + AckNum, c->lack);
+ flags |= (MinHdr / 4) << 12;
+ nput16(pkt + Flags, flags);
+ nput16(pkt + WinSize, ring_avail(&c->rx));
+ memcpy(pkt + MinHdr, buf, len);
+ nput16(pkt + Checksum, ip_checksumphdr(pkt, MinHdr + len, c->lip, c->rip, 6));
+
+ ipv4_send(pkt, MinHdr + len, (struct ipv4){
+ .proto = 6,
+ .src = c->lip,
+ .dst = c->rip,
+ .e.dst = &c->rmac,
+ });
+ free(pkt);
+}
+void tcp_listen(
+ uint16_t port,
+ void (*on_conn)(struct tcp_conn *, void *carg),
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg)
+{
+ struct tcp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lport = port;
+ c->lip = state.ip;
+ c->state = LISTEN;
+ c->on_conn = on_conn;
+ c->on_recv = on_recv;
+ c->on_close = on_close;
+ c->carg = carg;
+ // TODO setting the ring size super low loses every nth byte. probably a bug with ring_t itself!
+ c->rx = (ring_t){malloc(4096), 4096, 0, 0};
+ conns_append(c);
+}
+struct tcp_conn *tcpc_new(
+ struct tcp t,
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg)
+{
+ struct tcp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lip = t.ip.src ? t.ip.src : state.ip;
+ c->rip = t.ip.dst;
+ c->lport = t.src ? t.src : 50002; // TODO randomize source ports
+ c->rport = t.dst;
+ if (arpcache_get(c->rip, &c->rmac) < 0) {
+ // TODO wait for ARP reply
+ arp_request(c->rip);
+ if (arpcache_get(state.gateway, &c->rmac) < 0) {
+ eprintf("neither target nor gateway not in ARP cache, dropping");
+ free(c);
+ return NULL;
+ }
+ }
+
+ c->state = SYN_SENT;
+ c->on_recv = on_recv;
+ c->on_close = on_close;
+ c->carg = carg;
+ c->rx = (ring_t){malloc(4096), 4096, 0, 0};
+ conns_append(c);
+
+ tcpc_sendraw(c, FlagSYN, NULL, 0);
+ c->lseq++;
+ return c;
+}
+size_t tcpc_tryread(struct tcp_conn *c, void *buf, size_t len) {
+ if (!buf) return ring_used(&c->rx);
+ return ring_get(&c->rx, buf, len);
+}
+void tcpc_send(struct tcp_conn *c, const void *buf, size_t len) {
+ tcpc_sendraw(c, FlagACK | FlagPSH, buf, len);
+}
+static void tcpc_tryfree(struct tcp_conn *c) {
+ if (c->state == CLOSED && c->uclosed) {
+ if (c->next) c->next->link = c->link;
+ *c->link = c->next;
+ free(c->rx.buf);
+ free(c);
+ }
+}
+void tcpc_close(struct tcp_conn *c) {
+ /* ONLY FOR USE BY THE USER, drops their reference */
+ assert(!c->uclosed);
+ c->uclosed = true;
+ if (c->state != CLOSED && c->state != LAST_ACK && c->state != LISTEN) {
+ tcpc_sendraw(c, FlagFIN | FlagACK, NULL, 0);
+ c->state = LAST_ACK;
+ c->on_conn = NULL;
+ c->on_close = NULL;
+ }
+ tcpc_tryfree(c);
+}
+
+void tcp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ if (len < 20) return;
+ uint16_t srcport = nget16(buf + SrcPort);
+ uint16_t dstport = nget16(buf + DstPort);
+ uint32_t seq = nget32(buf + Seq);
+ uint32_t acknum = nget32(buf + AckNum);
+ uint16_t flags = nget16(buf + Flags);
+ // uint16_t winsize = nget16(buf + WinSize);
+ // uint16_t chksum = nget16(buf + Checksum);
+ uint16_t hdrlen = ((flags & FlagSize) >> 12) * 4;
+ if (hdrlen > len) return;
+ uint16_t payloadlen = len - hdrlen;
+
+ for (struct tcp_conn *iter = conns; iter; iter = iter->next) {
+ if (iter->state == CLOSED) continue;
+ if (iter->lport != dstport) continue;
+
+ if (iter->state == LISTEN && (flags & FlagAll) == FlagSYN) {
+ iter->state = SYN_RCVD;
+ iter->rip = ip.src;
+ iter->rport = srcport;
+ iter->lack = seq + 1;
+ memcpy(&iter->rmac, ip.e.src, sizeof(mac_t));
+ tcpc_sendraw(iter, FlagSYN | FlagACK, NULL, 0);
+ iter->lseq++;
+ if (iter->on_conn) iter->on_conn(iter, iter->carg);
+ return;
+ }
+
+ if (iter->rip == ip.src && iter->rport == srcport) {
+ // TODO doesn't handle seq/ack overflows
+ if (iter->state == SYN_SENT) {
+ if (flags & FlagSYN) {
+ iter->state = ESTABILISHED;
+ iter->lack = seq + 1;
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ return;
+ } else {
+ // TODO resend syn?
+ return;
+ }
+ }
+ if (flags & FlagACK) {
+ if (iter->rack < acknum)
+ iter->rack = acknum;
+ if (iter->state == LAST_ACK) {
+ // TODO check if ack has correct number
+ iter->state = CLOSED;
+ tcpc_tryfree(iter);
+ // TODO free (also after a timeout)
+ return;
+ }
+ }
+ if (iter->lack != seq && iter->lack - 1 != seq) {
+ eprintf("remote seq jumped by %d", seq - iter->lack);
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ return;
+ }
+ // TODO check if overflows window size
+ if (payloadlen) {
+ iter->lack = seq + payloadlen;
+ ring_put(&iter->rx, buf + hdrlen, payloadlen);
+ if (iter->on_recv) iter->on_recv(iter->carg);
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ }
+ if (flags & FlagFIN) {
+ // TODO should resend the packet until an ACK is received
+ // TODO duplicated in tcpc_close
+ tcpc_sendraw(iter, FlagFIN | FlagACK, NULL, 0);
+ iter->state = LAST_ACK;
+ if (iter->on_close) iter->on_close(iter->carg);
+ return;
+ }
+ return;
+ }
+ }
+
+ if ((flags & FlagRST) == 0) {
+ uint8_t *pkt = malloc(MinHdr);
+ memset(pkt, 0, MinHdr);
+ nput16(pkt + SrcPort, dstport);
+ nput16(pkt + DstPort, srcport);
+ nput32(pkt + Seq, acknum);
+ nput32(pkt + AckNum, seq + 1);
+ uint16_t pktflags = FlagRST | FlagACK;
+ pktflags |= (MinHdr / 4) << 12;
+ nput16(pkt + Flags, pktflags);
+ nput16(pkt + Checksum, ip_checksumphdr(pkt, MinHdr, ip.src, ip.dst, 6));
+
+ ipv4_send(pkt, MinHdr, (struct ipv4){
+ .proto = 6,
+ .src = ip.dst,
+ .dst = ip.src,
+ .e.dst = ip.e.src,
+ });
+ free(pkt);
+ }
+}
diff --git a/src/cmd/netstack/udp.c b/src/cmd/netstack/udp.c
new file mode 100644
index 0000000..3d560ae
--- /dev/null
+++ b/src/cmd/netstack/udp.c
@@ -0,0 +1,124 @@
+#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, **link;
+};
+static struct udp_conn *conns;
+static void conns_append(struct udp_conn *c) {
+ c->next = conns;
+ if (c->next)
+ c->next->link = &c->next;
+ c->link = &conns;
+ *c->link = c;
+}
+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;
+ conns_append(c);
+}
+struct udp_conn *udpc_new(
+ struct udp u,
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg)
+{
+ struct udp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lip = u.ip.src;
+ c->rip = u.ip.dst;
+ c->lport = u.src ? u.src : 50000; // TODO randomize source ports
+ c->rport = u.dst;
+ if (arpcache_get(c->rip, &c->rmac) < 0) {
+ // TODO make arp request, wait for reply
+ eprintf("not in ARP cache, unimplemented");
+ free(c);
+ return NULL;
+ }
+ c->on_recv = on_recv;
+ c->carg = carg;
+ conns_append(c);
+ return 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 udpc_close(struct udp_conn *c) {
+ if (c->next) c->next->link = c->link;
+ *(c->link) = c->next;
+ free(c);
+}
+
+
+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/cmd/netstack/util.c b/src/cmd/netstack/util.c
new file mode 100644
index 0000000..68092aa
--- /dev/null
+++ b/src/cmd/netstack/util.c
@@ -0,0 +1,60 @@
+#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;
+}
+
+uint16_t ip_checksumphdr(
+ const uint8_t *buf, size_t len,
+ uint32_t ip1, uint32_t ip2,
+ uint16_t proto)
+{
+ uint32_t c = (uint16_t)~ip_checksum(buf, len);
+ c += (ip1 & 0xFFFF) + (ip1 >> 16);
+ c += (ip2 & 0xFFFF) + (ip2 >> 16);
+ c += proto + len;
+ while (c > 0xFFFF) c = (c & 0xFFFF) + (c >> 16);
+ return ~c;
+}
+
+int ip_parse(const char *s, uint32_t *dest) {
+ if (!s) return -1;
+
+ uint32_t ip = strtol(s, (char**)&s, 0);
+ int parts = 1;
+ for (; parts < 4; parts++) {
+ if (!*s) break;
+ if (*s++ != '.') return -1;
+ ip <<= 8;
+ ip += strtol(s, (char**)&s, 0);
+ }
+ if (parts > 1)
+ ip = ((ip & 0xFFFFFF00) << 8 * (4 - parts)) | (ip & 0xFF);
+ *dest = ip;
+ return 0;
+}
diff --git a/src/cmd/netstack/util.h b/src/cmd/netstack/util.h
new file mode 100644
index 0000000..0b29560
--- /dev/null
+++ b/src/cmd/netstack/util.h
@@ -0,0 +1,44 @@
+#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);
+uint16_t ip_checksumphdr(
+ const uint8_t *buf, size_t len,
+ uint32_t ip1, uint32_t ip2,
+ uint16_t proto);
+/* 0 on success, negative failure */
+int ip_parse(const char *s, uint32_t *ip);
+
+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);
+}