summaryrefslogtreecommitdiff
path: root/src/cmd/netstack/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/netstack/fs.c')
-rw-r--r--src/cmd/netstack/fs.c320
1 files changed, 320 insertions, 0 deletions
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);
+}