From 424bcc25db6c3de73678fe43d44e4b408cd0434d From: Peter Mikkelsen 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 #include #include <9p.h> +#include #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 +#include +#include +#include +#include <9p.h> +#include + +#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); +}