diff options
Diffstat (limited to 'src/cmd/netstack/fs.c')
-rw-r--r-- | src/cmd/netstack/fs.c | 320 |
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); +} |