websock: added WebSocket client/server module

This commit is contained in:
Richard Aas 2014-04-10 11:50:57 +00:00
parent 38f4370a6c
commit 9891c74872
8 changed files with 832 additions and 3 deletions

View file

@ -18,7 +18,7 @@ include $(MK)
# List of modules
MODULES += sip sipevent sipreg sipsess
MODULES += uri http httpauth msg
MODULES += uri http httpauth msg websock
MODULES += stun turn ice
MODULES += natbd
MODULES += rtp sdp jbuf telev

View file

@ -61,6 +61,7 @@ Modules:
* turn testing Obtaining Relay Addresses from STUN (TURN)
* udp testing UDP transport
* uri testing Generic URI library
* websock unstable WebSocket Client and Server
legend:
"stable" - Code complete; Stable code and stable API
@ -111,6 +112,7 @@ Features:
* RFC 5780 - NAT Behaviour Discovery Using STUN
* RFC 6026 - Correct Transaction Handling for 2xx Resp. to SIP INVITE Requests
* RFC 6156 - TURN Extension for IPv6
* RFC 6455 - The WebSocket Protocol
* Symmetric RTP
* ITU-T G.711 Appendix I and Appendix II
* draft-ietf-bfcpbis-rfc4582bis-08

View file

@ -55,6 +55,7 @@ extern "C" {
#include "re_tls.h"
#include "re_turn.h"
#include "re_udp.h"
#include "re_websock.h"
#ifdef __cplusplus
}

View file

@ -122,6 +122,8 @@ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc);
int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
const char *uri, http_resp_h *resph, http_data_h *datah,
void *arg, const char *fmt, ...);
struct tcp_conn *http_req_tcp(struct http_req *req);
struct tls_conn *http_req_tls(struct http_req *req);
/* Server */

74
include/re_websock.h Normal file
View file

@ -0,0 +1,74 @@
/**
* @file re_websock.h The WebSocket Protocol
*
* Copyright (C) 2010 Creytiv.com
*/
enum {
WEBSOCK_VERSION = 13,
};
enum websock_opcode {
/* Data frames */
WEBSOCK_CONT = 0x0,
WEBSOCK_TEXT = 0x1,
WEBSOCK_BIN = 0x2,
/* Control frames */
WEBSOCK_CLOSE = 0x8,
WEBSOCK_PING = 0x9,
WEBSOCK_PONG = 0xa,
};
enum websock_scode {
WEBSOCK_NORMAL_CLOSURE = 1000,
WEBSOCK_GOING_AWAY = 1001,
WEBSOCK_PROTOCOL_ERROR = 1002,
WEBSOCK_UNSUPPORTED_DATA = 1003,
WEBSOCK_INVALID_PAYLOAD = 1007,
WEBSOCK_POLICY_VIOLATION = 1008,
WEBSOCK_MESSAGE_TOO_BIG = 1009,
WEBSOCK_EXTENSION_ERROR = 1010,
WEBSOCK_INTERNAL_ERROR = 1011,
};
struct websock_hdr {
unsigned fin:1;
unsigned rsv1:1;
unsigned rsv2:1;
unsigned rsv3:1;
unsigned opcode:4;
unsigned mask:1;
uint64_t len;
uint8_t mkey[4];
};
struct websock;
struct websock_conn;
typedef void (websock_estab_h)(void *arg);
typedef void (websock_recv_h)(const struct websock_hdr *hdr, struct mbuf *mb,
void *arg);
typedef void (websock_close_h)(int err, void *arg);
int websock_connect(struct websock_conn **connp, struct websock *sock,
struct http_cli *cli, const char *uri, unsigned kaint,
websock_estab_h *estabh, websock_recv_h *recvh,
websock_close_h *closeh, void *arg,
const char *fmt, ...);
int websock_accept(struct websock_conn **connp, struct websock *sock,
struct http_conn *htconn, const struct http_msg *msg,
unsigned kaint, websock_recv_h *recvh,
websock_close_h *closeh, void *arg);
int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
const char *fmt, ...);
int websock_close(struct websock_conn *conn, enum websock_scode scode,
const char *fmt, ...);
const struct sa *websock_peer(const struct websock_conn *conn);
typedef void (websock_shutdown_h)(void *arg);
int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth,
void *arg);
void websock_shutdown(struct websock *sock);

View file

@ -338,12 +338,14 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
&scheme, &host, NULL, &port, &path) || scheme.p != uri)
return EINVAL;
if (!pl_strcasecmp(&scheme, "http")) {
if (!pl_strcasecmp(&scheme, "http") ||
!pl_strcasecmp(&scheme, "ws")) {
secure = false;
defport = 80;
}
#ifdef USE_TLS
else if (!pl_strcasecmp(&scheme, "https")) {
else if (!pl_strcasecmp(&scheme, "https") ||
!pl_strcasecmp(&scheme, "wss")) {
secure = true;
defport = 443;
}
@ -455,3 +457,15 @@ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc)
return err;
}
struct tcp_conn *http_req_tcp(struct http_req *req)
{
return req ? req->tc : NULL;
}
struct tls_conn *http_req_tls(struct http_req *req)
{
return req ? req->sc : NULL;
}

7
src/websock/mod.mk Normal file
View file

@ -0,0 +1,7 @@
#
# mod.mk
#
# Copyright (C) 2010 Creytiv.com
#
SRCS += websock/websock.c

729
src/websock/websock.c Normal file
View file

@ -0,0 +1,729 @@
/**
* @file websock.c Implementation of The WebSocket Protocol
*
* Copyright (C) 2010 Creytiv.com
*/
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_tmr.h>
#include <re_tcp.h>
#include <re_tls.h>
#include <re_msg.h>
#include <re_http.h>
#include <re_base64.h>
#include <re_sha.h>
#include <re_sys.h>
#include <re_websock.h>
enum {
TIMEOUT_CLOSE = 10000,
BUFSIZE_MAX = 131072,
};
enum websock_state {
ACCEPTING = 0,
CONNECTING,
OPEN,
CLOSING,
CLOSED,
};
struct websock {
websock_shutdown_h *shuth;
void *arg;
bool shutdown;
};
struct websock_conn {
struct tmr tmr;
struct sa peer;
char nonce[24];
struct websock *sock;
struct tcp_conn *tc;
struct tls_conn *sc;
struct mbuf *mb;
struct http_req *req;
websock_estab_h *estabh;
websock_recv_h *recvh;
websock_close_h *closeh;
void *arg;
enum websock_state state;
unsigned kaint;
bool active;
};
static const char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static void timeout_handler(void *arg);
static void dummy_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb,
void *arg)
{
(void)hdr;
(void)mb;
(void)arg;
}
static void internal_close_handler(int err, void *arg)
{
struct websock_conn *conn = arg;
(void)err;
mem_deref(conn);
}
static void sock_destructor(void *arg)
{
struct websock *sock = arg;
if (sock->shutdown) {
sock->shutdown = false;
mem_ref(sock);
if (sock->shuth)
sock->shuth(sock->arg);
return;
}
}
static void conn_destructor(void *arg)
{
struct websock_conn *conn = arg;
if (conn->state == OPEN)
(void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away");
if (conn->state == CLOSING) {
conn->recvh = dummy_recv_handler;
conn->closeh = internal_close_handler;
conn->arg = conn;
tmr_start(&conn->tmr, TIMEOUT_CLOSE, timeout_handler, conn);
/* important: the hack below depends on this */
mem_ref(conn);
return;
}
tmr_cancel(&conn->tmr);
mem_deref(conn->sc);
mem_deref(conn->tc);
mem_deref(conn->mb);
mem_deref(conn->req);
mem_deref(conn->sock);
}
static void conn_close(struct websock_conn *conn, int err)
{
tmr_cancel(&conn->tmr);
conn->sc = mem_deref(conn->sc);
conn->tc = mem_deref(conn->tc);
conn->state = CLOSED;
conn->closeh(err, conn->arg);
}
static void timeout_handler(void *arg)
{
struct websock_conn *conn = arg;
conn_close(conn, ETIMEDOUT);
}
static void keepalive_handler(void *arg)
{
struct websock_conn *conn = arg;
tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
(void)websock_send(conn, WEBSOCK_PING, NULL);
}
static enum websock_scode websock_err2scode(int err)
{
switch (err) {
case EOVERFLOW: return WEBSOCK_MESSAGE_TOO_BIG;
case EPROTO: return WEBSOCK_PROTOCOL_ERROR;
case EBADMSG: return WEBSOCK_PROTOCOL_ERROR;
default: return WEBSOCK_INTERNAL_ERROR;
}
}
static int websock_decode(struct websock_hdr *hdr, struct mbuf *mb)
{
uint8_t v, *p;
size_t i;
if (mbuf_get_left(mb) < 2)
return ENODATA;
v = mbuf_read_u8(mb);
hdr->fin = v>>7 & 0x1;
hdr->rsv1 = v>>6 & 0x1;
hdr->rsv2 = v>>5 & 0x1;
hdr->rsv3 = v>>4 & 0x1;
hdr->opcode = v & 0x0f;
v = mbuf_read_u8(mb);
hdr->mask = v>>7 & 0x1;
hdr->len = v & 0x7f;
if (hdr->len == 126) {
if (mbuf_get_left(mb) < 2)
return ENODATA;
hdr->len = ntohs(mbuf_read_u16(mb));
}
else if (hdr->len == 127) {
if (mbuf_get_left(mb) < 8)
return ENODATA;
hdr->len = sys_ntohll(mbuf_read_u64(mb));
}
if (hdr->mask) {
if (mbuf_get_left(mb) < (4 + hdr->len))
return ENODATA;
hdr->mkey[0] = mbuf_read_u8(mb);
hdr->mkey[1] = mbuf_read_u8(mb);
hdr->mkey[2] = mbuf_read_u8(mb);
hdr->mkey[3] = mbuf_read_u8(mb);
for (i=0, p=mbuf_buf(mb); i<hdr->len; i++)
p[i] = p[i] ^ hdr->mkey[i%4];
}
else {
if (mbuf_get_left(mb) < hdr->len)
return ENODATA;
}
return 0;
}
static void recv_handler(struct mbuf *mb, void *arg)
{
struct websock_conn *conn = arg;
int err = 0;
if (conn->mb) {
const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
err = EOVERFLOW;
goto out;
}
conn->mb->pos = conn->mb->end;
err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
if (err)
goto out;
conn->mb->pos = pos;
}
else {
conn->mb = mem_ref(mb);
}
while (conn->mb) {
struct websock_hdr hdr;
size_t pos, end;
pos = conn->mb->pos;
err = websock_decode(&hdr, conn->mb);
if (err) {
if (err == ENODATA) {
conn->mb->pos = pos;
err = 0;
break;
}
goto out;
}
if (conn->active == hdr.mask) {
err = EPROTO;
goto out;
}
if (hdr.rsv1 || hdr.rsv2 || hdr.rsv3) {
err = EPROTO;
goto out;
}
mb = conn->mb;
end = mb->end;
mb->end = mb->pos + (size_t)hdr.len;
if (end > mb->end) {
struct mbuf *mbn = mbuf_alloc(end - mb->end);
if (!mbn) {
err = ENOMEM;
goto out;
}
(void)mbuf_write_mem(mbn, mb->buf + mb->end,
end - mb->end);
mbn->pos = 0;
conn->mb = mbn;
}
else {
conn->mb = NULL;
}
switch (hdr.opcode) {
case WEBSOCK_CONT:
case WEBSOCK_TEXT:
case WEBSOCK_BIN:
mem_ref(conn);
conn->recvh(&hdr, mb, conn->arg);
if (mem_nrefs(conn) == 1) {
if (conn->state == OPEN)
(void)websock_close(conn,
WEBSOCK_GOING_AWAY,
"Going Away");
/*
* This is a hack. We enforce CLOSING
* state so we know the connection will
* continue to live.
*/
conn->state = CLOSING;
}
mem_deref(conn);
break;
case WEBSOCK_CLOSE:
if (conn->state == OPEN)
(void)websock_send(conn, WEBSOCK_CLOSE, "%b",
mbuf_buf(mb), mbuf_get_left(mb));
conn_close(conn, 0);
mem_deref(mb);
return;
case WEBSOCK_PING:
(void)websock_send(conn, WEBSOCK_PONG, "%b",
mbuf_buf(mb), mbuf_get_left(mb));
break;
case WEBSOCK_PONG:
break;
default:
mem_deref(mb);
err = EPROTO;
goto out;
}
mem_deref(mb);
}
out:
if (err) {
(void)websock_close(conn, websock_err2scode(err), NULL);
conn_close(conn, err);
}
}
static void close_handler(int err, void *arg)
{
struct websock_conn *conn = arg;
conn_close(conn, err);
}
static int accept_print(struct re_printf *pf, const struct pl *key)
{
uint8_t digest[SHA_DIGEST_LENGTH];
SHA_CTX ctx;
SHA1_Init(&ctx);
SHA1_Update(&ctx, key->p, key->l);
SHA1_Update(&ctx, magic, sizeof(magic)-1);
SHA1_Final(digest, &ctx);
return base64_print(pf, digest, sizeof(digest));
}
static void http_resp_handler(int err, const struct http_msg *msg, void *arg)
{
struct websock_conn *conn = arg;
const struct http_hdr *hdr;
struct pl key;
char buf[32];
if (err || msg->scode != 101)
goto fail;
if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
goto fail;
if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
goto fail;
hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_ACCEPT);
if (!hdr)
goto fail;
key.p = conn->nonce;
key.l = sizeof(conn->nonce);
if (re_snprintf(buf, sizeof(buf), "%H", accept_print, &key) < 0)
goto fail;
if (pl_strcmp(&hdr->val, buf))
goto fail;
/* here we are ok */
conn->state = OPEN;
conn->tc = mem_ref(http_req_tcp(conn->req));
conn->sc = mem_ref(http_req_tls(conn->req));
(void)tcp_conn_peer_get(conn->tc, &conn->peer);
tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
conn->req = mem_deref(conn->req);
if (conn->kaint)
tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
conn->estabh(conn->arg);
return;
fail:
conn_close(conn, err ? err : EPROTO);
}
/* dummy HTTP data handler, this must be here so that HTTP client
* is not closing the underlying TCP-connection (which we need ..)
*/
static void http_data_handler(struct mbuf *mb, void *arg)
{
(void)mb;
(void)arg;
}
int websock_connect(struct websock_conn **connp, struct websock *sock,
struct http_cli *cli, const char *uri, unsigned kaint,
websock_estab_h *estabh, websock_recv_h *recvh,
websock_close_h *closeh, void *arg,
const char *fmt, ...)
{
struct websock_conn *conn;
uint8_t nonce[16];
va_list ap;
size_t len;
int err;
if (!connp || !sock || !cli || !uri || !estabh || !recvh || !closeh)
return EINVAL;
conn = mem_zalloc(sizeof(*conn), conn_destructor);
if (!conn)
return ENOMEM;
/* The nonce MUST be selected randomly for each connection */
rand_bytes(nonce, sizeof(nonce));
len = sizeof(conn->nonce);
err = base64_encode(nonce, sizeof(nonce), conn->nonce, &len);
if (err)
goto out;
conn->sock = mem_ref(sock);
conn->kaint = kaint;
conn->estabh = estabh;
conn->recvh = recvh;
conn->closeh = closeh;
conn->arg = arg;
conn->state = CONNECTING;
conn->active = true;
/* Protocol Handshake */
va_start(ap, fmt);
err = http_request(&conn->req, cli, "GET", uri,
http_resp_handler, http_data_handler, conn,
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: %b\r\n"
"Sec-WebSocket-Version: 13\r\n"
"%v"
"\r\n",
conn->nonce, sizeof(conn->nonce),
fmt, &ap);
va_end(ap);
if (err)
goto out;
out:
if (err)
mem_deref(conn);
else
*connp = conn;
return err;
}
int websock_accept(struct websock_conn **connp, struct websock *sock,
struct http_conn *htconn, const struct http_msg *msg,
unsigned kaint, websock_recv_h *recvh,
websock_close_h *closeh, void *arg)
{
const struct http_hdr *key;
struct websock_conn *conn;
int err;
if (!connp || !sock || !htconn || !msg || !recvh || !closeh)
return EINVAL;
if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
return EBADMSG;
if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
return EBADMSG;
if (!http_msg_hdr_has_value(msg, HTTP_HDR_SEC_WEBSOCKET_VERSION, "13"))
return EBADMSG;
key = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_KEY);
if (!key)
return EBADMSG;
conn = mem_zalloc(sizeof(*conn), conn_destructor);
if (!conn)
return ENOMEM;
err = http_reply(htconn, 101, "Switching Protocols",
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %H\r\n"
"\r\n",
accept_print, &key->val);
if (err)
goto out;
sa_cpy(&conn->peer, http_conn_peer(htconn));
conn->sock = mem_ref(sock);
conn->tc = mem_ref(http_conn_tcp(htconn));
conn->sc = mem_ref(http_conn_tls(htconn));
conn->kaint = kaint;
conn->recvh = recvh;
conn->closeh = closeh;
conn->arg = arg;
conn->state = OPEN;
conn->active = false;
tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
http_conn_close(htconn);
if (conn->kaint)
tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
out:
if (err)
mem_deref(conn);
else
*connp = conn;
return err;
}
static int websock_encode(struct mbuf *mb, bool fin,
enum websock_opcode opcode, bool mask, size_t len)
{
int err;
err = mbuf_write_u8(mb, (fin<<7) | (opcode & 0x0f));
if (len > 0xffff) {
err |= mbuf_write_u8(mb, (mask<<7) | 127);
err |= mbuf_write_u64(mb, sys_htonll(len));
}
else if (len > 125) {
err |= mbuf_write_u8(mb, (mask<<7) | 126);
err |= mbuf_write_u16(mb, htons(len));
}
else {
err |= mbuf_write_u8(mb, (mask<<7) | len);
}
if (mask) {
uint8_t mkey[4];
uint8_t *p;
size_t i;
rand_bytes(mkey, sizeof(mkey));
err |= mbuf_write_mem(mb, mkey, sizeof(mkey));
for (i=0, p=mbuf_buf(mb); i<len; i++)
p[i] = p[i] ^ mkey[i%4];
}
return err;
}
static int websock_vsend(struct websock_conn *conn, enum websock_opcode opcode,
enum websock_scode scode, const char *fmt, va_list ap)
{
const size_t hsz = conn->active ? 14 : 10;
size_t len, start;
struct mbuf *mb;
int err = 0;
if (conn->state != OPEN)
return ENOTCONN;
mb = mbuf_alloc(2048);
if (!mb)
return ENOMEM;
mb->pos = hsz;
if (scode)
err |= mbuf_write_u16(mb, htons(scode));
if (fmt)
err |= mbuf_vprintf(mb, fmt, ap);
if (err)
goto out;
len = mb->pos - hsz;
if (len > 0xffff)
start = mb->pos = 0;
else if (len > 125)
start = mb->pos = 6;
else
start = mb->pos = 8;
err = websock_encode(mb, true, opcode, conn->active, len);
if (err)
goto out;
mb->pos = start;
err = tcp_send(conn->tc, mb);
if (err)
goto out;
out:
mem_deref(mb);
return err;
}
int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
const char *fmt, ...)
{
va_list ap;
int err;
if (!conn)
return EINVAL;
va_start(ap, fmt);
err = websock_vsend(conn, opcode, 0, fmt, ap);
va_end(ap);
return err;
}
int websock_close(struct websock_conn *conn, enum websock_scode scode,
const char *fmt, ...)
{
va_list ap;
int err;
if (!conn)
return EINVAL;
if (!scode)
fmt = NULL;
va_start(ap, fmt);
err = websock_vsend(conn, WEBSOCK_CLOSE, scode, fmt, ap);
va_end(ap);
if (!err)
conn->state = CLOSING;
return err;
}
const struct sa *websock_peer(const struct websock_conn *conn)
{
return conn ? &conn->peer : NULL;
}
int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg)
{
struct websock *sock;
if (!sockp)
return EINVAL;
sock = mem_zalloc(sizeof(*sock), sock_destructor);
if (!sock)
return ENOMEM;
sock->shuth = shuth;
sock->arg = arg;
*sockp = sock;
return 0;
}
void websock_shutdown(struct websock *sock)
{
if (!sock || sock->shutdown)
return;
sock->shutdown = true;
mem_deref(sock);
}