blob: e7814775798dabc9519b7cb47860b5f4d7749095 [file] [log] [blame]
James Kuszmaul82f6c042021-01-17 11:30:16 -08001/**
2 * @file websock.c Implementation of The WebSocket Protocol
3 *
4 * Copyright (C) 2010 Creytiv.com
5 */
6
7#include <re_types.h>
8#include <re_fmt.h>
9#include <re_mem.h>
10#include <re_mbuf.h>
11#include <re_sa.h>
12#include <re_list.h>
13#include <re_tmr.h>
14#include <re_srtp.h>
15#include <re_tcp.h>
16#include <re_tls.h>
17#include <re_msg.h>
18#include <re_http.h>
19#include <re_base64.h>
20#include <re_sha.h>
21#include <re_sys.h>
22#include <re_websock.h>
23
24
25enum {
26 TIMEOUT_CLOSE = 10000,
27 BUFSIZE_MAX = 131072,
28};
29
30enum websock_state {
31 ACCEPTING = 0,
32 CONNECTING,
33 OPEN,
34 CLOSING,
35 CLOSED,
36};
37
38struct websock {
39 websock_shutdown_h *shuth;
40 void *arg;
41 bool shutdown;
42};
43
44struct websock_conn {
45 struct tmr tmr;
46 struct sa peer;
47 char nonce[24];
48 struct websock *sock;
49 struct tcp_conn *tc;
50 struct tls_conn *sc;
51 struct mbuf *mb;
52 struct http_req *req;
53 websock_estab_h *estabh;
54 websock_recv_h *recvh;
55 websock_close_h *closeh;
56 void *arg;
57 enum websock_state state;
58 unsigned kaint;
59 bool active;
60};
61
62
63static const char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
64
65
66static void timeout_handler(void *arg);
67
68
69static void dummy_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb,
70 void *arg)
71{
72 (void)hdr;
73 (void)mb;
74 (void)arg;
75}
76
77
78static void internal_close_handler(int err, void *arg)
79{
80 struct websock_conn *conn = arg;
81 (void)err;
82
83 mem_deref(conn);
84}
85
86
87static void sock_destructor(void *arg)
88{
89 struct websock *sock = arg;
90
91 if (sock->shutdown) {
92 sock->shutdown = false;
93 mem_ref(sock);
94 if (sock->shuth)
95 sock->shuth(sock->arg);
96 return;
97 }
98}
99
100
101static void conn_destructor(void *arg)
102{
103 struct websock_conn *conn = arg;
104
105 if (conn->state == OPEN)
106 (void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away");
107
108 if (conn->state == CLOSING) {
109
110 conn->recvh = dummy_recv_handler;
111 conn->closeh = internal_close_handler;
112 conn->arg = conn;
113
114 tmr_start(&conn->tmr, TIMEOUT_CLOSE, timeout_handler, conn);
115
116 /* important: the hack below depends on this */
117 mem_ref(conn);
118 return;
119 }
120
121 tmr_cancel(&conn->tmr);
122 mem_deref(conn->sc);
123 mem_deref(conn->tc);
124 mem_deref(conn->mb);
125 mem_deref(conn->req);
126 mem_deref(conn->sock);
127}
128
129
130static void conn_close(struct websock_conn *conn, int err)
131{
132 tmr_cancel(&conn->tmr);
133 conn->sc = mem_deref(conn->sc);
134 conn->tc = mem_deref(conn->tc);
135 conn->state = CLOSED;
136
137 conn->closeh(err, conn->arg);
138}
139
140
141static void timeout_handler(void *arg)
142{
143 struct websock_conn *conn = arg;
144
145 conn_close(conn, ETIMEDOUT);
146}
147
148
149static void keepalive_handler(void *arg)
150{
151 struct websock_conn *conn = arg;
152
153 tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
154
155 (void)websock_send(conn, WEBSOCK_PING, NULL);
156}
157
158
159static enum websock_scode websock_err2scode(int err)
160{
161 switch (err) {
162
163 case EOVERFLOW: return WEBSOCK_MESSAGE_TOO_BIG;
164 case EPROTO: return WEBSOCK_PROTOCOL_ERROR;
165 case EBADMSG: return WEBSOCK_PROTOCOL_ERROR;
166 default: return WEBSOCK_INTERNAL_ERROR;
167 }
168}
169
170
171static int websock_decode(struct websock_hdr *hdr, struct mbuf *mb)
172{
173 uint8_t v, *p;
174 size_t i;
175
176 if (mbuf_get_left(mb) < 2)
177 return ENODATA;
178
179 v = mbuf_read_u8(mb);
180 hdr->fin = v>>7 & 0x1;
181 hdr->rsv1 = v>>6 & 0x1;
182 hdr->rsv2 = v>>5 & 0x1;
183 hdr->rsv3 = v>>4 & 0x1;
184 hdr->opcode = v & 0x0f;
185
186 v = mbuf_read_u8(mb);
187 hdr->mask = v>>7 & 0x1;
188 hdr->len = v & 0x7f;
189
190 if (hdr->len == 126) {
191
192 if (mbuf_get_left(mb) < 2)
193 return ENODATA;
194
195 hdr->len = ntohs(mbuf_read_u16(mb));
196 }
197 else if (hdr->len == 127) {
198
199 if (mbuf_get_left(mb) < 8)
200 return ENODATA;
201
202 hdr->len = sys_ntohll(mbuf_read_u64(mb));
203 }
204
205 if (hdr->mask) {
206
207 if (mbuf_get_left(mb) < (4 + hdr->len))
208 return ENODATA;
209
210 hdr->mkey[0] = mbuf_read_u8(mb);
211 hdr->mkey[1] = mbuf_read_u8(mb);
212 hdr->mkey[2] = mbuf_read_u8(mb);
213 hdr->mkey[3] = mbuf_read_u8(mb);
214
215 for (i=0, p=mbuf_buf(mb); i<hdr->len; i++)
216 p[i] = p[i] ^ hdr->mkey[i%4];
217 }
218 else {
219 if (mbuf_get_left(mb) < hdr->len)
220 return ENODATA;
221 }
222
223 return 0;
224}
225
226
227static void recv_handler(struct mbuf *mb, void *arg)
228{
229 struct websock_conn *conn = arg;
230 int err = 0;
231
232 if (conn->mb) {
233
234 const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
235
236 if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
237 err = EOVERFLOW;
238 goto out;
239 }
240
241 conn->mb->pos = conn->mb->end;
242
243 err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
244 if (err)
245 goto out;
246
247 conn->mb->pos = pos;
248 }
249 else {
250 conn->mb = mem_ref(mb);
251 }
252
253 while (conn->mb) {
254
255 struct websock_hdr hdr;
256 size_t pos, end;
257
258 pos = conn->mb->pos;
259
260 err = websock_decode(&hdr, conn->mb);
261 if (err) {
262 if (err == ENODATA) {
263 conn->mb->pos = pos;
264 err = 0;
265 break;
266 }
267
268 goto out;
269 }
270
271 if (conn->active == hdr.mask) {
272 err = EPROTO;
273 goto out;
274 }
275
276 if (hdr.rsv1 || hdr.rsv2 || hdr.rsv3) {
277 err = EPROTO;
278 goto out;
279 }
280
281 mb = conn->mb;
282
283 end = mb->end;
284 mb->end = mb->pos + (size_t)hdr.len;
285
286 if (end > mb->end) {
287 struct mbuf *mbn = mbuf_alloc(end - mb->end);
288 if (!mbn) {
289 err = ENOMEM;
290 goto out;
291 }
292
293 (void)mbuf_write_mem(mbn, mb->buf + mb->end,
294 end - mb->end);
295 mbn->pos = 0;
296
297 conn->mb = mbn;
298 }
299 else {
300 conn->mb = NULL;
301 }
302
303 switch (hdr.opcode) {
304
305 case WEBSOCK_CONT:
306 case WEBSOCK_TEXT:
307 case WEBSOCK_BIN:
308 mem_ref(conn);
309 conn->recvh(&hdr, mb, conn->arg);
310
311 if (mem_nrefs(conn) == 1) {
312
313 if (conn->state == OPEN)
314 (void)websock_close(conn,
315 WEBSOCK_GOING_AWAY,
316 "Going Away");
317
318 /*
319 * This is a hack. We enforce CLOSING
320 * state so we know the connection will
321 * continue to live.
322 */
323 conn->state = CLOSING;
324 }
325 mem_deref(conn);
326 break;
327
328 case WEBSOCK_CLOSE:
329 if (conn->state == OPEN)
330 (void)websock_send(conn, WEBSOCK_CLOSE, "%b",
331 mbuf_buf(mb), mbuf_get_left(mb));
332 conn_close(conn, 0);
333 mem_deref(mb);
334 return;
335
336 case WEBSOCK_PING:
337 (void)websock_send(conn, WEBSOCK_PONG, "%b",
338 mbuf_buf(mb), mbuf_get_left(mb));
339 break;
340
341 case WEBSOCK_PONG:
342 break;
343
344 default:
345 mem_deref(mb);
346 err = EPROTO;
347 goto out;
348 }
349
350 mem_deref(mb);
351 }
352
353 out:
354 if (err) {
355 (void)websock_close(conn, websock_err2scode(err), NULL);
356 conn_close(conn, err);
357 }
358}
359
360
361static void close_handler(int err, void *arg)
362{
363 struct websock_conn *conn = arg;
364
365 conn_close(conn, err);
366}
367
368
369static int accept_print(struct re_printf *pf, const struct pl *key)
370{
371 uint8_t digest[SHA_DIGEST_LENGTH];
372 SHA_CTX ctx;
373
374 SHA1_Init(&ctx);
375 SHA1_Update(&ctx, key->p, key->l);
376 SHA1_Update(&ctx, magic, sizeof(magic)-1);
377 SHA1_Final(digest, &ctx);
378
379 return base64_print(pf, digest, sizeof(digest));
380}
381
382
383static void http_resp_handler(int err, const struct http_msg *msg, void *arg)
384{
385 struct websock_conn *conn = arg;
386 const struct http_hdr *hdr;
387 struct pl key;
388 char buf[32];
389
390 if (err || msg->scode != 101)
391 goto fail;
392
393 if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
394 goto fail;
395
396 if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
397 goto fail;
398
399 hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_ACCEPT);
400 if (!hdr)
401 goto fail;
402
403 key.p = conn->nonce;
404 key.l = sizeof(conn->nonce);
405
406 if (re_snprintf(buf, sizeof(buf), "%H", accept_print, &key) < 0)
407 goto fail;
408
409 if (pl_strcmp(&hdr->val, buf))
410 goto fail;
411
412 /* here we are ok */
413
414 conn->state = OPEN;
415 (void)tcp_conn_peer_get(conn->tc, &conn->peer);
416
417 if (conn->kaint)
418 tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
419
420 conn->estabh(conn->arg);
421 return;
422
423 fail:
424 conn_close(conn, err ? err : EPROTO);
425}
426
427
428static void http_conn_handler(struct tcp_conn *tc, struct tls_conn *sc,
429 void *arg)
430{
431 struct websock_conn *conn = arg;
432
433 conn->tc = mem_ref(tc);
434 conn->sc = mem_ref(sc);
435
436 tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
437}
438
439
440int websock_connect(struct websock_conn **connp, struct websock *sock,
441 struct http_cli *cli, const char *uri, unsigned kaint,
442 websock_estab_h *estabh, websock_recv_h *recvh,
443 websock_close_h *closeh, void *arg,
444 const char *fmt, ...)
445{
446 struct websock_conn *conn;
447 uint8_t nonce[16];
448 va_list ap;
449 size_t len;
450 int err;
451
452 if (!connp || !sock || !cli || !uri || !estabh || !recvh || !closeh)
453 return EINVAL;
454
455 conn = mem_zalloc(sizeof(*conn), conn_destructor);
456 if (!conn)
457 return ENOMEM;
458
459 /* The nonce MUST be selected randomly for each connection */
460 rand_bytes(nonce, sizeof(nonce));
461
462 len = sizeof(conn->nonce);
463
464 err = base64_encode(nonce, sizeof(nonce), conn->nonce, &len);
465 if (err)
466 goto out;
467
468 conn->sock = mem_ref(sock);
469 conn->kaint = kaint;
470 conn->estabh = estabh;
471 conn->recvh = recvh;
472 conn->closeh = closeh;
473 conn->arg = arg;
474 conn->state = CONNECTING;
475 conn->active = true;
476
477 /* Protocol Handshake */
478 va_start(ap, fmt);
479 err = http_request(&conn->req, cli, "GET", uri,
480 http_resp_handler, NULL, conn,
481 "Upgrade: websocket\r\n"
482 "Connection: upgrade\r\n"
483 "Sec-WebSocket-Key: %b\r\n"
484 "Sec-WebSocket-Version: 13\r\n"
485 "%v"
486 "\r\n",
487 conn->nonce, sizeof(conn->nonce),
488 fmt, &ap);
489 va_end(ap);
490 if (err)
491 goto out;
492
493 http_req_set_conn_handler(conn->req, http_conn_handler);
494
495 out:
496 if (err)
497 mem_deref(conn);
498 else
499 *connp = conn;
500
501 return err;
502}
503
504
505int websock_accept(struct websock_conn **connp, struct websock *sock,
506 struct http_conn *htconn, const struct http_msg *msg,
507 unsigned kaint, websock_recv_h *recvh,
508 websock_close_h *closeh, void *arg)
509{
510 const struct http_hdr *key;
511 struct websock_conn *conn;
512 int err;
513
514 if (!connp || !sock || !htconn || !msg || !recvh || !closeh)
515 return EINVAL;
516
517 if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
518 return EBADMSG;
519
520 if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
521 return EBADMSG;
522
523 if (!http_msg_hdr_has_value(msg, HTTP_HDR_SEC_WEBSOCKET_VERSION, "13"))
524 return EBADMSG;
525
526 key = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_KEY);
527 if (!key)
528 return EBADMSG;
529
530 conn = mem_zalloc(sizeof(*conn), conn_destructor);
531 if (!conn)
532 return ENOMEM;
533
534 err = http_reply(htconn, 101, "Switching Protocols",
535 "Upgrade: websocket\r\n"
536 "Connection: Upgrade\r\n"
537 "Sec-WebSocket-Accept: %H\r\n"
538 "\r\n",
539 accept_print, &key->val);
540 if (err)
541 goto out;
542
543 sa_cpy(&conn->peer, http_conn_peer(htconn));
544 conn->sock = mem_ref(sock);
545 conn->tc = mem_ref(http_conn_tcp(htconn));
546 conn->sc = mem_ref(http_conn_tls(htconn));
547 conn->kaint = kaint;
548 conn->recvh = recvh;
549 conn->closeh = closeh;
550 conn->arg = arg;
551 conn->state = OPEN;
552 conn->active = false;
553
554 tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
555 http_conn_close(htconn);
556
557 if (conn->kaint)
558 tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
559
560 out:
561 if (err)
562 mem_deref(conn);
563 else
564 *connp = conn;
565
566 return err;
567}
568
569
570static int websock_encode(struct mbuf *mb, bool fin,
571 enum websock_opcode opcode, bool mask, size_t len)
572{
573 int err;
574
575 err = mbuf_write_u8(mb, (fin<<7) | (opcode & 0x0f));
576
577 if (len > 0xffff) {
578 err |= mbuf_write_u8(mb, (mask<<7) | 127);
579 err |= mbuf_write_u64(mb, sys_htonll(len));
580 }
581 else if (len > 125) {
582 err |= mbuf_write_u8(mb, (mask<<7) | 126);
583 err |= mbuf_write_u16(mb, htons(len));
584 }
585 else {
586 err |= mbuf_write_u8(mb, (mask<<7) | len);
587 }
588
589 if (mask) {
590 uint8_t mkey[4];
591 uint8_t *p;
592 size_t i;
593
594 rand_bytes(mkey, sizeof(mkey));
595
596 err |= mbuf_write_mem(mb, mkey, sizeof(mkey));
597
598 for (i=0, p=mbuf_buf(mb); i<len; i++)
599 p[i] = p[i] ^ mkey[i%4];
600 }
601
602 return err;
603}
604
605
606static int websock_vsend(struct websock_conn *conn, enum websock_opcode opcode,
607 enum websock_scode scode, const char *fmt, va_list ap)
608{
609 const size_t hsz = conn->active ? 14 : 10;
610 size_t len, start;
611 struct mbuf *mb;
612 int err = 0;
613
614 if (conn->state != OPEN)
615 return ENOTCONN;
616
617 mb = mbuf_alloc(2048);
618 if (!mb)
619 return ENOMEM;
620
621 mb->pos = hsz;
622
623 if (scode)
624 err |= mbuf_write_u16(mb, htons(scode));
625 if (fmt)
626 err |= mbuf_vprintf(mb, fmt, ap);
627 if (err)
628 goto out;
629
630 len = mb->pos - hsz;
631
632 if (len > 0xffff)
633 start = mb->pos = 0;
634 else if (len > 125)
635 start = mb->pos = 6;
636 else
637 start = mb->pos = 8;
638
639 err = websock_encode(mb, true, opcode, conn->active, len);
640 if (err)
641 goto out;
642
643 mb->pos = start;
644
645 err = tcp_send(conn->tc, mb);
646 if (err)
647 goto out;
648
649 out:
650 mem_deref(mb);
651
652 return err;
653}
654
655
656int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
657 const char *fmt, ...)
658{
659 va_list ap;
660 int err;
661
662 if (!conn)
663 return EINVAL;
664
665 va_start(ap, fmt);
666 err = websock_vsend(conn, opcode, 0, fmt, ap);
667 va_end(ap);
668
669 return err;
670}
671
672
673int websock_close(struct websock_conn *conn, enum websock_scode scode,
674 const char *fmt, ...)
675{
676 va_list ap;
677 int err;
678
679 if (!conn)
680 return EINVAL;
681
682 if (!scode)
683 fmt = NULL;
684
685 va_start(ap, fmt);
686 err = websock_vsend(conn, WEBSOCK_CLOSE, scode, fmt, ap);
687 va_end(ap);
688
689 if (!err)
690 conn->state = CLOSING;
691
692 return err;
693}
694
695
696const struct sa *websock_peer(const struct websock_conn *conn)
697{
698 return conn ? &conn->peer : NULL;
699}
700
701
702int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg)
703{
704 struct websock *sock;
705
706 if (!sockp)
707 return EINVAL;
708
709 sock = mem_zalloc(sizeof(*sock), sock_destructor);
710 if (!sock)
711 return ENOMEM;
712
713 sock->shuth = shuth;
714 sock->arg = arg;
715
716 *sockp = sock;
717
718 return 0;
719}
720
721
722void websock_shutdown(struct websock *sock)
723{
724 if (!sock || sock->shutdown)
725 return;
726
727 sock->shutdown = true;
728 mem_deref(sock);
729}