summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sites/pmikkelsen.com/_files/websocket-webfs.patch565
-rw-r--r--sites/pmikkelsen.com/plan9/webfs-websocket.md77
2 files changed, 642 insertions, 0 deletions
diff --git a/sites/pmikkelsen.com/_files/websocket-webfs.patch b/sites/pmikkelsen.com/_files/websocket-webfs.patch
new file mode 100644
index 0000000..77c44b0
--- /dev/null
+++ b/sites/pmikkelsen.com/_files/websocket-webfs.patch
@@ -0,0 +1,565 @@
+From 424bcc25db6c3de73678fe43d44e4b408cd0434d
+From: Peter Mikkelsen <petermikkelsen10@gmail.com>
+Date: Wed, 23 Jun 2021 15:44:24 +0000
+Subject: [PATCH] Initial buggy version of adding websocket support to webfs
+
+
+It can create connections but doesn't handle errors very well yet.
+---
+diff ce73821f3575921e24f839b21c7be60520a9dc42 424bcc25db6c3de73678fe43d44e4b408cd0434d
+--- a/sys/src/cmd/webfs/fns.h Mon Jun 21 17:38:11 2021
++++ b/sys/src/cmd/webfs/fns.h Wed Jun 23 17:44:24 2021
+@@ -41,3 +41,6 @@
+ int authenticate(Url *u, Url *ru, char *method, char *s);
+ void flushauth(Url *u, char *t);
+ void http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost);
++
++/* websocket */
++void websocket(int fd, Buq *qbody, Buq *qpost);
+--- a/sys/src/cmd/webfs/fs.c Mon Jun 21 17:38:11 2021
++++ b/sys/src/cmd/webfs/fs.c Wed Jun 23 17:44:24 2021
+@@ -4,6 +4,7 @@
+ #include <fcall.h>
+ #include <thread.h>
+ #include <9p.h>
++#include <libsec.h>
+
+ #include "dat.h"
+ #include "fns.h"
+@@ -19,10 +20,12 @@
+ Url *baseurl;
+ Url *url;
+ Key *hdr;
++ int wantwebsocket;
+
+ int obody; /* body opend */
+ int cbody; /* body closed */
+ Buq *qbody;
++ Buq *websocketin; /* websocket input */
+ };
+
+ struct Webfid
+@@ -121,6 +124,8 @@
+
+ buclose(cl->qbody, 0);
+ bufree(cl->qbody);
++ buclose(cl->websocketin, 0);
++ bufree(cl->websocketin);
+
+ while(k = cl->hdr){
+ cl->hdr = k->next;
+@@ -384,6 +389,25 @@
+ if(cl->qbody == nil){
+ char *m;
+
++ if(f->level == Qbody && (!strcmp(cl->url->scheme, "ws") || !strcmp(cl->url->scheme, "wss"))){
++ cl->wantwebsocket = 1;
++ cl->websocketin = bualloc(64*1024);
++ if(!lookkey(cl->hdr, "Upgrade"))
++ cl->hdr = addkey(cl->hdr, "Upgrade", "websocket");
++ if(!lookkey(cl->hdr, "Connection"))
++ cl->hdr = addkey(cl->hdr, "Connection", "upgrade");
++ if(!lookkey(cl->hdr, "Sec-WebSocket-Version"))
++ cl->hdr = addkey(cl->hdr, "Sec-WebSocket-Version", "13");
++ if(!lookkey(cl->hdr, "Sec-WebSocket-Key")){
++ uchar nonce[16];
++ char *b64nonce;
++ genrandom(nonce, 16);
++ b64nonce = smprint("%.*[", 16, nonce);
++ cl->hdr = addkey(cl->hdr, "Sec-WebSocket-Key", b64nonce);
++ free(b64nonce);
++ }
++ }
++
+ if(cl->url == nil){
+ respond(r, "no url set");
+ return;
+@@ -413,7 +437,7 @@
+ if(agent && !lookkey(cl->hdr, "User-Agent"))
+ cl->hdr = addkey(cl->hdr, "User-Agent", agent);
+
+- http(m, cl->url, cl->hdr, cl->qbody, f->buq);
++ http(m, cl->url, cl->hdr, cl->qbody, cl->wantwebsocket ? cl->websocketin : f->buq);
+ cl->request[0] = 0;
+ cl->url = nil;
+ cl->hdr = nil;
+@@ -683,6 +707,11 @@
+ case Qpost:
+ bureq(f->buq, r);
+ return;
++ case Qbody:
++ if(f->client->wantwebsocket){
++ bureq(f->client->websocketin, r);
++ return;
++ }
+ }
+ respond(r, "not implemented");
+ }
+--- a/sys/src/cmd/webfs/http.c Mon Jun 21 17:38:11 2021
++++ b/sys/src/cmd/webfs/http.c Wed Jun 23 17:44:24 2021
+@@ -131,7 +131,7 @@
+ if(strcmp(proxy->scheme, "https") == 0)
+ fd = tlswrap(fd, proxy->host);
+ } else {
+- if(strcmp(u->scheme, "https") == 0)
++ if(strcmp(u->scheme, "https") == 0 || strcmp(u->scheme, "wss") == 0)
+ fd = tlswrap(fd, u->host);
+ }
+ if(fd < 0){
+@@ -520,7 +520,7 @@
+ void
+ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
+ {
+- int i, l, n, try, pid, fd, cfd, needlength, chunked, retry, nobody, badauth;
++ int i, l, n, try, pid, fd, cfd, needlength, chunked, retry, nobody, badauth, wantwebsocket;
+ char *s, *x, buf[8192+2], status[256], method[16], *host;
+ vlong length, offset;
+ Url ru, tu, *nu;
+@@ -549,8 +549,13 @@
+ break;
+ }
+
++ if(!strcmp(u->scheme, "ws") || !strcmp(u->scheme, "wss"))
++ wantwebsocket = 1;
++ else
++ wantwebsocket = 0;
++
+ notify(catch);
+- if(qpost){
++ if(qpost && !wantwebsocket){
+ /* file for spooling the postbody if we need to restart the request */
+ snprint(buf, sizeof(buf), "/tmp/http.%d.%d.post", getppid(), getpid());
+ fd = create(buf, OEXCL|ORDWR|ORCLOSE, 0600);
+@@ -565,7 +570,7 @@
+ badauth = 0;
+ for(try = 0; try < 12; try++){
+ strcpy(status, "0 No status");
+- if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){
++ if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https") && strcmp(u->scheme, "ws") && strcmp(u->scheme, "wss"))){
+ werrstr("bad url scheme");
+ break;
+ }
+@@ -591,7 +596,7 @@
+
+ length = 0;
+ chunked = 0;
+- if(qpost){
++ if(qpost && !wantwebsocket){
+ /* have to read it to temp file to figure out the length */
+ if(fd >= 0 && needlength && lookkey(shdr, "Content-Length") == nil){
+ seek(fd, 0, 2);
+@@ -700,7 +705,7 @@
+ goto Retry;
+ }
+
+- if(qpost && !h->tunnel){
++ if(qpost && !h->tunnel && !wantwebsocket){
+ h->cancel = 0;
+ if((pid = rfork(RFMEM|RFPROC)) <= 0){
+ int ifd;
+@@ -802,8 +807,42 @@
+ goto Status;
+ }
+ goto Error;
+- case 100: /* Continue */
+ case 101: /* Switching Protocols */
++ if(wantwebsocket){
++ int ok = 1;
++ char *wskey = lookkey(shdr, "Sec-WebSocket-Key");
++
++ k = getkey(rhdr, "Upgrade");
++ if(k == nil || cistrcmp(k->val, "websocket") != 0)
++ ok = 0;
++
++ k = getkey(rhdr, "Connection");
++ if(k == nil && cistrcmp(k->val, "upgrade") != 0)
++ ok = 0;
++
++ k = getkey(rhdr, "Sec-WebSocket-Accept");
++ if(k == nil || wskey == nil)
++ ok = 0;
++ else{
++ uchar digest[SHA1dlen];
++ char *str = smprint("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", wskey);
++
++ sha1((uchar*)str, strlen(str), digest, nil);
++ char *val = smprint("%.*[", SHA1dlen, digest);
++ if(strcmp(val, k->val) != 0)
++ ok = 0;
++ free(str);
++ free(val);
++ }
++
++ if(ok){
++ qbody->url = u; u = nil;
++ qbody->hdr = rhdr; rhdr = nil;
++ websocket(h->fd, qbody, qpost);
++ }
++ goto Error;
++ }
++ case 100: /* Continue */
+ case 102: /* Processing */
+ case 103: /* Early Hints */
+ while(k = rhdr){
+--- a/sys/src/cmd/webfs/mkfile Mon Jun 21 17:38:11 2021
++++ b/sys/src/cmd/webfs/mkfile Wed Jun 23 17:44:24 2021
+@@ -3,6 +3,6 @@
+ TARG=webfs
+
+ HFILES=fns.h dat.h
+-OFILES=sub.$O url.$O buq.$O http.$O fs.$O
++OFILES=sub.$O url.$O buq.$O http.$O websocket.$O fs.$O
+
+ </sys/src/cmd/mkone
+--- /dev/null Mon Jun 21 23:24:08 2021
++++ b/sys/src/cmd/webfs/websocket.c Wed Jun 23 17:44:24 2021
+@@ -0,0 +1,349 @@
++#include <u.h>
++#include <libc.h>
++#include <fcall.h>
++#include <thread.h>
++#include <9p.h>
++#include <libsec.h>
++
++#include "dat.h"
++#include "fns.h"
++
++typedef struct Frame Frame;
++typedef struct Message Message;
++
++struct Frame
++{
++ int final;
++ int opcode;
++ int masked;
++ uchar maskkey[4];
++ uvlong length;
++ uchar *data;
++
++ Frame *next; /* Chain frames in a message together */
++};
++
++struct Message
++{
++ int type;
++ uvlong length;
++ uchar *data;
++};
++
++enum {
++ ContinueFrame = 0x0,
++ TextFrame = 0x1,
++ BinaryFrame = 0x2,
++ CloseFrame = 0x8,
++ PingFrame = 0x9,
++ PongFrame = 0xA,
++};
++
++void writemessage(int, Message *);
++
++void
++maskframe(Frame *f)
++{
++ uvlong i;
++ for(i = 0; i < f->length; i++)
++ f->data[i] = f->data[i] ^ f->maskkey[i%4];
++}
++
++Frame *
++readframe(int fd)
++{
++ Frame *f = malloc(sizeof(Frame));
++ long n;
++ int offset;
++ uchar len;
++
++ uchar buf[4096];
++
++Again:
++ offset = 0;
++ n = read(fd, buf, 2);
++ if(n != 2)
++ goto Error;
++
++ f->final = (buf[offset] >> 7) & 0x1;
++ /* ignore rsv1, rsv2, rsv3 */
++ f->opcode = buf[offset] & 0x0F;
++ offset++;
++
++ f->masked = (buf[offset] >> 7) & 0x1;
++ len = buf[offset] & 0x7F;
++ offset++;
++
++ if(len <= 125)
++ f->length = len;
++ else if(len == 126){
++ n = read(fd, buf+offset, 2);
++ if(n != 2)
++ goto Error;
++
++ f->length = buf[offset++] << 8;
++ f->length |= buf[offset++];
++ }else if(len == 127){
++ n = read(fd, buf+offset, 8);
++ if(n != 8)
++ goto Error;
++ f->length = (uvlong)buf[offset++] << 56;
++ f->length |= (uvlong)buf[offset++] << 48;
++ f->length |= (uvlong)buf[offset++] << 40;
++ f->length |= (uvlong)buf[offset++] << 32;
++ f->length |= buf[offset++] << 24;
++ f->length |= buf[offset++] << 16;
++ f->length |= buf[offset++] << 8;
++ f->length |= buf[offset++];
++ }
++
++ if(f->masked){
++ n = read(fd, buf+offset, 4);
++ if(n != 4)
++ goto Error;
++
++ f->maskkey[0] = buf[offset++];
++ f->maskkey[1] = buf[offset++];
++ f->maskkey[2] = buf[offset++];
++ f->maskkey[3] = buf[offset];
++ }
++
++ f->data = malloc(f->length);
++ readn(fd, f->data, f->length);
++ if(f->masked)
++ maskframe(f);
++
++ if(f->opcode == PingFrame){
++ Message *m = malloc(sizeof(Message));
++ m->type = PongFrame;
++ m->length = f->length;
++ m->data = f->data;
++ writemessage(fd, m); /* SHOULD LOCK */
++ free(m);
++ free(f->data);
++ goto Again;
++ }
++
++ return f;
++Error:
++ exits("Readframe failed");
++ return nil;
++}
++
++Message *
++framestomessage(Frame *frames)
++{
++ Message *m = malloc(sizeof(Message));
++ uvlong length = 0;
++ uvlong offset = 0;
++
++ Frame *f;
++ for(f = frames; f != nil; f = f->next)
++ length += f->length;
++
++ m->type = frames->opcode;
++ m->length = length;
++ m->data = malloc(length);
++ for(f = frames; f != nil; f = f->next){
++ memcpy(m->data + offset, f->data, f->length);
++ offset += f->length;
++ }
++
++ return m;
++}
++
++Message *
++readmsg(int fd)
++{
++ Frame *frames = readframe(fd);
++ Frame *last = frames;
++ Message *m;
++
++ while(!last->final){
++ last->next = readframe(fd);
++ last = last->next;
++ }
++ last->next = nil;
++
++ m = framestomessage(frames);
++ while(frames){
++ Frame *f = frames->next;
++ free(frames->data);
++ free(frames);
++ frames = f;
++ }
++ return m;
++}
++
++void
++writeframe(int fd, Frame *f)
++{
++ uchar *buf;
++ uvlong totsize = 0;
++ uvlong offset = 0;
++ int lenbytes = 0;
++
++ totsize += 2; /* Always there */
++ if(f->length == 126)
++ lenbytes = 2;
++ else if(f->length == 127)
++ lenbytes = 8;
++ totsize += lenbytes;
++
++ if(f->masked)
++ totsize += 4;
++
++ totsize += f->length;
++
++ buf = malloc(totsize);
++
++ buf[offset] = f->final << 7;
++ buf[offset++] |= f->opcode;
++ buf[offset] = f->masked << 7;
++
++ if(lenbytes == 0)
++ buf[offset++] |= f->length;
++ else if(lenbytes == 2)
++ buf[offset++] |= 126;
++ else if(lenbytes == 8)
++ buf[offset++] |= 127;
++
++ if(lenbytes == 2){
++ buf[offset++] = 0xFF & (f->length >> 8);
++ buf[offset++] = 0xFF & f->length;
++ }else if(lenbytes == 8){
++ buf[offset++] = 0xFF & (f->length >> 56);
++ buf[offset++] = 0xFF & (f->length >> 48);
++ buf[offset++] = 0xFF & (f->length >> 40);
++ buf[offset++] = 0xFF & (f->length >> 32);
++ buf[offset++] = 0xFF & (f->length >> 24);
++ buf[offset++] = 0xFF & (f->length >> 16);
++ buf[offset++] = 0xFF & (f->length >> 8);
++ buf[offset++] = 0xFF & f->length;
++ }
++
++ if(f->masked){
++ buf[offset++] = f->maskkey[0];
++ buf[offset++] = f->maskkey[1];
++ buf[offset++] = f->maskkey[2];
++ buf[offset++] = f->maskkey[3];
++ }
++
++ memcpy(buf + offset, f->data, f->length);
++ write(fd, buf, totsize);
++}
++
++void
++writemessage(int fd, Message *m)
++{
++ /* Could split up the message into frames but not right now. */
++
++ Frame *f = malloc(sizeof(Frame));
++ f->final = 1;
++ f->opcode = m->type;
++ f->masked = 1;
++ genrandom(f->maskkey, 4);
++ f->length = m->length;
++ f->data = malloc(f->length);
++ memcpy(f->data, m->data, f->length);
++ maskframe(f);
++ f->next = nil;
++
++ writeframe(fd, f);
++ free(f->data);
++ free(f);
++}
++
++void
++websocketrecv(int fd, Buq *qbody)
++{
++ /*
++ * Plan: Read frames and when we have a whole message do:
++ * 1) If control, act on it
++ * 2) If data, format as text and buwrite it
++ */
++ while(1){
++ Message *m = readmsg(fd);
++ //print("Received message type=%d length=%ulld\n", m->type, m->length);
++
++ if(m->type == TextFrame || m->type == BinaryFrame){
++ char *header;
++ if(m->type == TextFrame)
++ header = smprint("t %0.20ulld", m->length);
++ else
++ header = smprint("b %0.20ulld", m->length);
++ buwrite(qbody, header, 22);
++ buwrite(qbody, m->data, m->length);
++ }
++ free(m->data);
++ free(m);
++ }
++}
++
++void
++websocketsend(int fd, Buq *qpost)
++{
++ /* buread messages and pack them up in frames and send them */
++ char buf[64];
++ long n;
++ Message *m;
++ while(1){
++ n = buread(qpost, buf, 22);
++ if(n != 22){
++ buclose(qpost, "Header short");
++ return;
++ }
++ buf[n] = 0;
++
++ m = malloc(sizeof(Message));
++ if(buf[0] == 't')
++ m->type = TextFrame;
++ else if(buf[0] == 'b')
++ m->type = BinaryFrame;
++
++ char *ret;
++ m->length = strtoull(buf+2, &ret, 10);
++ if(ret != buf+22){
++ buclose(qpost, "Header format wrong");
++ return;
++ }
++
++ m->data = malloc(m->length);
++ n = 0;
++ while(n < m->length){
++ uvlong missing = m->length - n;
++ n += buread(qpost, m->data + n, (missing < 8192) ? missing : 8192);
++ }
++
++ writemessage(fd, m);
++ free(m->data);
++ free(m);
++ }
++}
++
++void
++websocket(int fd, Buq *qbody, Buq *qpost)
++{
++ buwrite(qbody, "websocket ready\n", 16);
++ switch(rfork(RFPROC|RFMEM)){
++ default:
++ websocketrecv(fd, qbody);
++ break;
++ case 0:
++ websocketsend(fd, qpost);
++ goto End;
++ case -1:
++ buclose(qbody, "can't fork");
++ bufree(qbody);
++ buclose(qpost, "can't fork");
++ bufree(qpost);
++ break;
++ }
++End:
++ close(fd);
++ buclose(qbody, nil);
++ bufree(qbody);
++ buclose(qpost, nil);
++ bufree(qpost);
++ exits(nil);
++}
diff --git a/sites/pmikkelsen.com/plan9/webfs-websocket.md b/sites/pmikkelsen.com/plan9/webfs-websocket.md
new file mode 100644
index 0000000..c229f95
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/webfs-websocket.md
@@ -0,0 +1,77 @@
+# Creating websocket connections via 9front's webfs
+
+With the following patch (note: work in progress, contains bugs), 9front's webfs can be used
+to create websocket connections. The patch is available [here](/_files/websocket-webfs.patch).
+
+## The idea
+
+Creating a websocket connection is done just like creating a http request is, except the
+url must use scheme `wss://` or `ws://`, and the `body` file must be opened read-write to
+allow both sending and receiving.
+
+The very first message to be read from `body` is always "websocket ready", meaning the connection has been
+established. After that the actual communication begins.
+
+Webfs itself takes care of the control messages, so only the data messages gets delivered to the user, in a textual
+format that is easy to parse. Each message read and writen to `body` consists of a 22 byte header in the following format
+
+ b nnnnnnnnnnnnnnnnnnnn
+
+or
+
+ t nnnnnnnnnnnnnnnnnnnn
+
+for binary and text messages respectively. The 20 last bytes are a 0-padded ascii number in base 10, telling the size of the message body,
+which follows directly after the header.
+
+## Example program
+
+To demonstrate, the following rc script connects to `wss://echo.websocket.org` which is a service that echos everything
+back to the user.
+
+ #!/bin/rc
+
+ rfork en
+
+ webfs
+
+ fn sendtextmsg{
+ awk 'BEGIN{printf "t %0.20d%s", length(ARGV[1]), ARGV[1]}' $1 >[1=0]
+ }
+
+ fn readheader{
+ header=`{read -c 22}
+ length=`{echo $header(2) p | dc}
+ echo $header(1) $length
+ }
+
+ fn handlemsg{
+ echo Handling message:
+ echo ' TYPE :' $1
+ echo ' LENGTH:' $2
+ echo ' BODY :' $3
+ }
+
+ <>/mnt/web/clone {
+ d=/mnt/web/^`{sed 1q}
+
+ echo url wss://echo.websocket.org >[1=0]
+
+ <>$d/body {
+ read -n 1 # Read the ready message
+
+ sendtextmsg 'First message sent'
+ while(header=`{readheader}){
+ echo Header: $header
+ body=`''{read -c $header(2)}
+ handlemsg $header(1) $header(2) $body
+ sleep 2
+ sendtextmsg 'Echo test'
+ }
+ }
+ }
+
+## Bugs
+
+Yes. The code doesn't really handle errors or connection shutdowns yet. Also, I need to add proper cleanup code
+around in different places. \ No newline at end of file