diff options
author | dzwdz | 2023-08-14 18:51:07 +0200 |
---|---|---|
committer | dzwdz | 2023-08-14 18:51:07 +0200 |
commit | 642b5fb0007b64c77d186fcb018d571152ee1d47 (patch) | |
tree | 1c466461f3602d306be309a053edae558ef2568e /src/cmd/netstack | |
parent | 8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff) |
reorganization: first steps
Diffstat (limited to 'src/cmd/netstack')
-rw-r--r-- | src/cmd/netstack/arp.c | 151 | ||||
-rw-r--r-- | src/cmd/netstack/ether.c | 59 | ||||
-rw-r--r-- | src/cmd/netstack/fs.c | 320 | ||||
-rw-r--r-- | src/cmd/netstack/icmp.c | 34 | ||||
-rw-r--r-- | src/cmd/netstack/ipv4.c | 216 | ||||
-rw-r--r-- | src/cmd/netstack/netstack.c | 53 | ||||
-rw-r--r-- | src/cmd/netstack/proto.h | 107 | ||||
-rw-r--r-- | src/cmd/netstack/tcp.c | 268 | ||||
-rw-r--r-- | src/cmd/netstack/udp.c | 124 | ||||
-rw-r--r-- | src/cmd/netstack/util.c | 60 | ||||
-rw-r--r-- | src/cmd/netstack/util.h | 44 |
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 = ðer_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); +} |