summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordzwdz2022-08-23 17:58:42 +0200
committerdzwdz2022-08-23 17:58:42 +0200
commit03c5dd9462492e291c6a49b88e1cd9ab34d86b6f (patch)
tree8c8d8e73c8c07b83f5aede18d83671369d0f7589 /src
parentfcdadf5df39e1d72f9ac79fa384fc6b98be0b1aa (diff)
user/netstack: TCP listen and close
Diffstat (limited to 'src')
-rw-r--r--src/shared/include/camellia/errno.h1
-rw-r--r--src/user/app/netstack/fs.c59
-rw-r--r--src/user/app/netstack/ipv4.c3
-rw-r--r--src/user/app/netstack/proto.h14
-rw-r--r--src/user/app/netstack/tcp.c180
-rw-r--r--src/user/app/netstack/udp.c23
-rw-r--r--src/user/app/netstack/util.c16
-rw-r--r--src/user/app/netstack/util.h4
8 files changed, 279 insertions, 21 deletions
diff --git a/src/shared/include/camellia/errno.h b/src/shared/include/camellia/errno.h
index e3f4d34..2c5594b 100644
--- a/src/shared/include/camellia/errno.h
+++ b/src/shared/include/camellia/errno.h
@@ -10,3 +10,4 @@
#define ENOTEMPTY 9
#define EACCES 10
#define EMFILE 11 /* All file descriptors taken. */
+#define ECONNRESET 12
diff --git a/src/user/app/netstack/fs.c b/src/user/app/netstack/fs.c
index 19e5bfd..cc4491d 100644
--- a/src/user/app/netstack/fs.c
+++ b/src/user/app/netstack/fs.c
@@ -6,19 +6,21 @@
* ARP cache (currently read-only)
* /net/0.0.0.0/connect/1.2.3.4/udp/53
* connect from 0.0.0.0 (any ip) to 1.2.3.4 on udp port 53
- * /net/0.0.0.0/listen/udp/53
+ * /net/0.0.0.0/listen/{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/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,
};
@@ -35,10 +37,31 @@ struct handle {
struct udp_conn *c;
struct strqueue *rx, *rxlast;
} udp;
+ struct {
+ struct tcp_conn *c;
+ } tcp;
+ bool dead;
handle_t reqh;
};
+static void tcp_listen_callback(struct tcp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->tcp.c = c;
+ _syscall_fs_respond(h->reqh, h, 0, 0);
+ h->reqh = -1;
+}
+
+static void tcp_close_callback(void *arg) {
+ struct handle *h = arg;
+ h->dead = true;
+ if (h->reqh >= 0) {
+ _syscall_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;
@@ -66,11 +89,11 @@ static void udp_recv_callback(const void *buf, size_t len, void *arg) {
}
}
-static void udp_recv_enqueue(struct handle *h, handle_t reqh) {
+static void 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) {
+ } else if (h->type == H_UDP && 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);
@@ -87,10 +110,12 @@ static void fs_open(handle_t reqh, char *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);
}
@@ -118,12 +143,23 @@ static void fs_open(handle_t reqh, char *path) {
h = malloc(sizeof *h);
memset(h, 0, sizeof *h);
h->type = H_UDP;
- h->udp.c = NULL;
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_close_callback, h);
+ return;
+ }
+ }
} else if (strcmp(verb, "connect") == 0) {
if (ip_parse(strtok_r(NULL, "/", &save), &dstip) < 0)
respond(NULL, -1);
@@ -172,6 +208,10 @@ void fs_thread(void *arg) { (void)arg;
}
break;
case VFSOP_READ:
+ if (h->dead) {
+ _syscall_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
switch (h->type) {
case H_ETHER: {
struct ethq *qe;
@@ -180,8 +220,9 @@ void fs_thread(void *arg) { (void)arg;
qe->next = ether_queue;
ether_queue = qe;
break;}
+ case H_TCP:
case H_UDP:
- udp_recv_enqueue(h, reqh);
+ recv_enqueue(h, reqh);
break;
case H_ARP:
arp_fsread(reqh, res.offset);
@@ -191,6 +232,10 @@ void fs_thread(void *arg) { (void)arg;
}
break;
case VFSOP_WRITE:
+ if (h->dead) {
+ _syscall_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
switch (h->type) {
case H_ETHER:
ret = _syscall_write(state.raw_h, buf, res.len, 0, 0);
@@ -207,8 +252,8 @@ void fs_thread(void *arg) { (void)arg;
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);
+ if (h->type == H_TCP) tcpc_close(h->tcp.c);
+ if (h->type == H_UDP) udpc_close(h->udp.c);
free(h);
_syscall_fs_respond(reqh, NULL, -1, 0);
break;
diff --git a/src/user/app/netstack/ipv4.c b/src/user/app/netstack/ipv4.c
index 3243d3c..1336dc1 100644
--- a/src/user/app/netstack/ipv4.c
+++ b/src/user/app/netstack/ipv4.c
@@ -148,7 +148,8 @@ static void fragmented_free(struct fragmented *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;
+ case 0x06: tcp_parse(buf, len, ip); break;
+ case 0x11: udp_parse(buf, len, ip); break;
}
}
diff --git a/src/user/app/netstack/proto.h b/src/user/app/netstack/proto.h
index 68dccad..0c4338b 100644
--- a/src/user/app/netstack/proto.h
+++ b/src/user/app/netstack/proto.h
@@ -31,6 +31,11 @@ struct ipv4 {
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;
@@ -81,3 +86,12 @@ struct udp_conn *udpc_new(
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_close)(void *carg),
+ void *carg);
+void tcpc_close(struct tcp_conn *);
diff --git a/src/user/app/netstack/tcp.c b/src/user/app/netstack/tcp.c
new file mode 100644
index 0000000..71e82f2
--- /dev/null
+++ b/src/user/app/netstack/tcp.c
@@ -0,0 +1,180 @@
+/* Welcome to spaghetti land. */
+#include "proto.h"
+#include "util.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_RCVD,
+ 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_close)(void *carg);
+ void *carg;
+};
+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_send(struct tcp_conn *c, uint16_t flags) {
+ uint8_t *pkt = malloc(MinHdr);
+ memset(pkt, 0, MinHdr);
+
+ nput16(pkt + SrcPort, c->lport);
+ nput16(pkt + DstPort, c->rport);
+ nput32(pkt + Seq, c->lseq);
+ nput32(pkt + AckNum, c->lack);
+ flags |= (MinHdr / 4) << 12;
+ nput16(pkt + Flags, flags);
+ nput16(pkt + WinSize, 4096);
+ nput16(pkt + Checksum, ip_checksumphdr(pkt, MinHdr, c->lip, c->rip, 6));
+
+ ipv4_send(pkt, MinHdr, (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_close)(void *carg),
+ void *carg)
+{
+ struct tcp_conn *c = malloc(sizeof *c);
+ c->lport = port;
+ c->lip = state.ip;
+ c->state = LISTEN;
+ c->on_conn = on_conn;
+ c->on_close = on_close;
+ c->carg = carg;
+ conns_append(c);
+}
+void tcpc_close(struct tcp_conn *c) {
+ /* ONLY FOR USE BY THE USER, drops their reference */
+ if (!c->uclosed) {
+ c->uclosed = true;
+ tcpc_send(c, FlagFIN | FlagACK);
+ c->state = LAST_ACK;
+ c->on_conn = NULL;
+ c->on_close = NULL;
+ }
+}
+
+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_send(iter, FlagSYN | FlagACK);
+ 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 (flags & FlagACK) {
+ if (iter->rack < acknum)
+ iter->rack = acknum;
+ if (iter->state == LAST_ACK) {
+ iter->state = CLOSED; // TODO check if ack has correct number
+ // 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_send(iter, FlagACK);
+ return;
+ }
+ iter->lack = seq + 1;
+ if (flags & FlagFIN) {
+ // TODO should resend the packet until an ACK is received
+ // TODO duplicated in tcpc_close
+ tcpc_send(iter, FlagFIN | FlagACK);
+ iter->state = LAST_ACK;
+ if (iter->on_close) iter->on_close(iter->carg);
+ return;
+ }
+ return;
+ }
+ }
+
+ 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/user/app/netstack/udp.c b/src/user/app/netstack/udp.c
index 46d09de..3d560ae 100644
--- a/src/user/app/netstack/udp.c
+++ b/src/user/app/netstack/udp.c
@@ -17,14 +17,15 @@ struct udp_conn {
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 *next, **link;
};
-struct udp_conn *conns;
+static struct udp_conn *conns;
static void conns_append(struct udp_conn *c) {
c->next = conns;
- if (c->next) *(c->next->prev) = c;
- c->prev = &conns;
- *c->prev = c;
+ if (c->next)
+ c->next->link = &c->next;
+ c->link = &conns;
+ *c->link = c;
}
void udp_listen(
uint16_t port,
@@ -79,8 +80,8 @@ void udpc_send(struct udp_conn *c, const void *buf, size_t len) {
free(pkt);
}
void udpc_close(struct udp_conn *c) {
- if (c->next) c->next->prev = c->prev;
- *(c->prev) = c->next;
+ if (c->next) c->next->link = c->link;
+ *(c->link) = c->next;
free(c);
}
@@ -98,10 +99,10 @@ void udp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
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)
+ 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);
diff --git a/src/user/app/netstack/util.c b/src/user/app/netstack/util.c
index e9d3118..68092aa 100644
--- a/src/user/app/netstack/util.c
+++ b/src/user/app/netstack/util.c
@@ -25,8 +25,20 @@ uint16_t ip_checksum(const uint8_t *buf, size_t len) {
buf += 2; len -= 2;
}
if (len) c += (*buf) << 8;
- while (c > 0xFFFF)
- c = (c & 0xFFFF) + (c >> 16);
+ 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;
}
diff --git a/src/user/app/netstack/util.h b/src/user/app/netstack/util.h
index b8fa5c6..0b29560 100644
--- a/src/user/app/netstack/util.h
+++ b/src/user/app/netstack/util.h
@@ -8,6 +8,10 @@
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);