mirror of https://github.com/sipwise/rtpengine.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1184 lines
34 KiB
1184 lines
34 KiB
#include "websocket.h"
|
|
#include <libwebsockets.h>
|
|
#include <assert.h>
|
|
#include <json-glib/json-glib.h>
|
|
#include "log.h"
|
|
#include "main.h"
|
|
#include "str.h"
|
|
#include "cli.h"
|
|
#include "control_ng.h"
|
|
#include "statistics.h"
|
|
#include "janus.h"
|
|
|
|
|
|
struct websocket_message;
|
|
struct websocket_conn;
|
|
|
|
|
|
struct websocket_output {
|
|
GString *str;
|
|
size_t str_done;
|
|
enum lws_write_protocol protocol;
|
|
int http_status;
|
|
const char *content_type;
|
|
ssize_t content_length;
|
|
};
|
|
|
|
struct websocket_conn {
|
|
// used in the single threaded libwebsockets context
|
|
struct lws *wsi;
|
|
endpoint_t endpoint;
|
|
char *uri; // for websocket connections only
|
|
struct websocket_message *wm; // while in progress
|
|
|
|
// multithreaded message processing
|
|
mutex_t lock;
|
|
unsigned int jobs;
|
|
GQueue messages;
|
|
cond_t cond;
|
|
GHashTable *janus_sessions;
|
|
|
|
// output buffer - also protected by lock
|
|
GQueue output_q;
|
|
};
|
|
|
|
struct websocket_ng_buf {
|
|
struct obj obj;
|
|
GString *body;
|
|
char addr[64];
|
|
str cmd;
|
|
endpoint_t endpoint;
|
|
};
|
|
|
|
|
|
static GQueue websocket_vhost_configs;
|
|
static struct lws_context *websocket_context;
|
|
static GThreadPool *websocket_threads;
|
|
static mutex_t websocket_callback_lock = MUTEX_STATIC_INIT;
|
|
static mutex_t websocket_service_lock = MUTEX_STATIC_INIT;
|
|
|
|
|
|
static struct websocket_message *websocket_message_new(struct websocket_conn *wc) {
|
|
struct websocket_message *wm = g_slice_alloc0(sizeof(*wm));
|
|
wm->body = g_string_new("");
|
|
wm->wc = wc;
|
|
return wm;
|
|
}
|
|
|
|
|
|
static void websocket_message_free(struct websocket_message **wm) {
|
|
if ((*wm)->body)
|
|
g_string_free((*wm)->body, TRUE);
|
|
if ((*wm)->uri)
|
|
free((*wm)->uri);
|
|
g_slice_free1(sizeof(**wm), *wm);
|
|
*wm = NULL;
|
|
}
|
|
|
|
|
|
static struct websocket_output *websocket_output_new(void) {
|
|
struct websocket_output *wo = g_slice_alloc0(sizeof(*wo));
|
|
// str remains NULL -> unused output slot
|
|
return wo;
|
|
}
|
|
|
|
static void websocket_output_free(void *p) {
|
|
struct websocket_output *wo = p;
|
|
if (wo->str)
|
|
g_string_free(wo->str, TRUE);
|
|
g_slice_free1(sizeof(*wo), wo);
|
|
}
|
|
|
|
|
|
// appends to output buffer without triggering a response - unlocked
|
|
static void __websocket_queue_raw(struct websocket_conn *wc, const char *msg, size_t len) {
|
|
struct websocket_output *wo = g_queue_peek_tail(&wc->output_q);
|
|
|
|
if (!wo->str) {
|
|
wo->str = g_string_new("");
|
|
// allocate pre-buffer
|
|
g_string_set_size(wo->str, LWS_PRE);
|
|
wo->str_done = LWS_PRE;
|
|
}
|
|
|
|
if (msg && len)
|
|
g_string_append_len(wo->str, msg, len);
|
|
}
|
|
|
|
|
|
// appends to output buffer without triggering a response
|
|
void websocket_queue_raw(struct websocket_conn *wc, const char *msg, size_t len) {
|
|
LOCK(&wc->lock);
|
|
__websocket_queue_raw(wc, msg, len);
|
|
}
|
|
|
|
|
|
// num bytes in output buffer
|
|
size_t websocket_queue_len(struct websocket_conn *wc) {
|
|
LOCK(&wc->lock);
|
|
|
|
size_t ret = 0;
|
|
for (GList *l = wc->output_q.head; l; l = l->next) {
|
|
struct websocket_output *wo = l->data;
|
|
ret += (wo->str->len - LWS_PRE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// adds data to output buffer (can be null) and optionally triggers specified response
|
|
void websocket_write_raw(struct websocket_conn *wc, const char *msg, size_t len,
|
|
enum lws_write_protocol protocol, bool done)
|
|
{
|
|
mutex_lock(&wc->lock);
|
|
__websocket_queue_raw(wc, msg, len);
|
|
struct websocket_output *wo = g_queue_peek_tail(&wc->output_q);
|
|
wo->protocol = protocol;
|
|
g_queue_push_tail(&wc->output_q, websocket_output_new());
|
|
|
|
mutex_unlock(&wc->lock);
|
|
|
|
if (done) {
|
|
// Sadly lws_callback_on_writable() doesn't do any internal
|
|
// locking, therefore we must protect it against a concurrently
|
|
// running lws_service(), as well as against other threads
|
|
// invoking lws_callback_on_writable().
|
|
//
|
|
// Acquire the callback lock first, which is normally unlocked,
|
|
// then wake up the service thread and try to break out of
|
|
// lws_service(). The service thread holds the service lock
|
|
// while lws_service() is executing and releases it as soon as
|
|
// lws_service() is done. We therefore try to acquire the
|
|
// service lock here, which blocks us until lws_service() is
|
|
// actually done. At this point the service thread will try to
|
|
// acquire the callback lock, which is still held by us here,
|
|
// and so the service thread will block until we are done
|
|
// calling lws_callback_on_writable(). Finally we release both
|
|
// locks, which allows the service thread to resume
|
|
// lws_service().
|
|
//
|
|
// The suggested approach of using
|
|
// LWS_CALLBACK_EVENT_WAIT_CANCELLED together with a queue and
|
|
// then calling lws_callback_on_writable() from the service
|
|
// thread is not usable as libwebsockets 2.0 doesn't support
|
|
// LWS_CALLBACK_EVENT_WAIT_CANCELLED.
|
|
|
|
mutex_lock(&websocket_callback_lock);
|
|
lws_cancel_service(websocket_context);
|
|
|
|
mutex_lock(&websocket_service_lock);
|
|
lws_callback_on_writable(wc->wsi);
|
|
|
|
mutex_unlock(&websocket_service_lock);
|
|
mutex_unlock(&websocket_callback_lock);
|
|
}
|
|
}
|
|
|
|
|
|
// adds data to output buffer (can be null) and triggers specified response: http or binary websocket
|
|
void websocket_write_http_len(struct websocket_conn *wc, const char *msg, size_t len, bool done) {
|
|
websocket_write_raw(wc, msg, len, LWS_WRITE_HTTP, done);
|
|
}
|
|
void websocket_write_http(struct websocket_conn *wc, const char *msg, bool done) {
|
|
websocket_write_http_len(wc, msg, msg ? strlen(msg) : 0, done);
|
|
}
|
|
void websocket_write_text(struct websocket_conn *wc, const char *msg, bool done) {
|
|
websocket_write_raw(wc, msg, strlen(msg), LWS_WRITE_TEXT, done);
|
|
}
|
|
void websocket_write_binary(struct websocket_conn *wc, const char *msg, size_t len, bool done) {
|
|
websocket_write_raw(wc, msg, len, LWS_WRITE_BINARY, done);
|
|
}
|
|
|
|
|
|
void websocket_write_next(struct websocket_conn *wc) {
|
|
LOCK(&wc->lock);
|
|
g_queue_push_tail(&wc->output_q, websocket_output_new());
|
|
}
|
|
|
|
|
|
static const char *websocket_echo_process(struct websocket_message *wm) {
|
|
ilogs(http, LOG_DEBUG, "Returning %lu bytes websocket echo from %s", (unsigned long) wm->body->len,
|
|
endpoint_print_buf(&wm->wc->endpoint));
|
|
websocket_write_binary(wm->wc, wm->body->str, wm->body->len, true);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void websocket_message_push(struct websocket_conn *wc, websocket_message_func_t func) {
|
|
ilogs(http, LOG_DEBUG, "Adding HTTP/WS message to processing queue");
|
|
|
|
LOCK(&wc->lock);
|
|
|
|
struct websocket_message *wm = wc->wm;
|
|
assert(wm != NULL);
|
|
wm->func = func;
|
|
|
|
g_queue_push_tail(&wc->messages, wm);
|
|
wc->jobs++;
|
|
g_thread_pool_push(websocket_threads, wc, NULL);
|
|
|
|
wc->wm = websocket_message_new(wc);
|
|
}
|
|
|
|
|
|
static void websocket_process(void *p, void *up) {
|
|
struct websocket_conn *wc = p;
|
|
|
|
mutex_lock(&wc->lock);
|
|
struct websocket_message *wm = g_queue_pop_head(&wc->messages);
|
|
mutex_unlock(&wc->lock);
|
|
|
|
assert(wm != NULL);
|
|
|
|
gettimeofday(&rtpe_now, NULL);
|
|
|
|
const char *err = wm->func(wm);
|
|
// this may trigger a cleanup/free in another thread, which will then block until our
|
|
// job count has been decremented
|
|
|
|
websocket_message_free(&wm);
|
|
mutex_lock(&wc->lock);
|
|
assert(wc->jobs >= 1);
|
|
wc->jobs--;
|
|
cond_signal(&wc->cond);
|
|
mutex_unlock(&wc->lock);
|
|
|
|
if (err)
|
|
ilogs(http, LOG_ERR, "Error while processing HTTP/WS message: %s", err);
|
|
}
|
|
|
|
|
|
static const char *__websocket_write_http_response(struct websocket_conn *wc, int status,
|
|
const char *content_type, ssize_t content_length)
|
|
{
|
|
uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
|
|
*end = &buf[sizeof(buf) - LWS_PRE - 1];
|
|
|
|
if (lws_add_http_header_status(wc->wsi, status, &p, end))
|
|
return "Failed to add HTTP status";
|
|
if (content_type)
|
|
if (lws_add_http_header_by_token(wc->wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
|
(const unsigned char *) content_type,
|
|
strlen(content_type), &p, end))
|
|
return "Failed to add content-type";
|
|
if (content_length >= 0)
|
|
if (lws_add_http_header_content_length(wc->wsi, content_length, &p, end))
|
|
return "Failed to add HTTP headers to response";
|
|
|
|
if (lws_add_http_header_by_name(wc->wsi,
|
|
(unsigned char *) "Access-Control-Allow-Origin:",
|
|
(unsigned char *) "*", 1, &p, end))
|
|
return "Failed to add CORS header";
|
|
if (lws_add_http_header_by_name(wc->wsi,
|
|
(unsigned char *) "Access-Control-Max-Age:",
|
|
(unsigned char *) "86400", 5, &p, end))
|
|
return "Failed to add CORS header";
|
|
if (lws_add_http_header_by_name(wc->wsi,
|
|
(unsigned char *) "Access-Control-Allow-Methods:",
|
|
(unsigned char *) "GET, POST", 9, &p, end))
|
|
return "Failed to add CORS header";
|
|
if (lws_add_http_header_by_name(wc->wsi,
|
|
(unsigned char *) "Access-Control-Allow-Headers:",
|
|
(unsigned char *) "Content-Type", 12, &p, end))
|
|
return "Failed to add CORS header";
|
|
|
|
if (lws_finalize_http_header(wc->wsi, &p, end))
|
|
return "Failed to write HTTP headers";
|
|
|
|
size_t len = p - start;
|
|
if (lws_write(wc->wsi, start, len, LWS_WRITE_HTTP_HEADERS) != len)
|
|
return "Failed to write HTTP headers";
|
|
|
|
return NULL;
|
|
}
|
|
static int websocket_dequeue(struct websocket_conn *wc) {
|
|
if (!wc)
|
|
return 0;
|
|
|
|
int is_http = 0;
|
|
|
|
mutex_lock(&wc->lock);
|
|
struct websocket_output *wo;
|
|
struct lws *wsi = wc->wsi;
|
|
while ((wo = g_queue_pop_head(&wc->output_q))) {
|
|
// used buffer slot?
|
|
if (!wo->str)
|
|
goto next;
|
|
|
|
if (wo->http_status) {
|
|
const char *err = __websocket_write_http_response(wc, wo->http_status,
|
|
wo->content_type, wo->content_length);
|
|
if (err) {
|
|
ilogs(http, LOG_ERR, "Failed to write HTTP response headers: %s", err);
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
// allocate post-buffer
|
|
g_string_set_size(wo->str, wo->str->len + LWS_SEND_BUFFER_POST_PADDING);
|
|
size_t to_send = wo->str->len - wo->str_done - LWS_SEND_BUFFER_POST_PADDING;
|
|
if (to_send) {
|
|
if (to_send > 10000)
|
|
ilogs(http, LOG_DEBUG, "Writing %lu bytes to LWS", (unsigned long) to_send);
|
|
else
|
|
ilogs(http, LOG_DEBUG, "Writing back to LWS: '%.*s'",
|
|
(int) to_send, wo->str->str + wo->str_done);
|
|
size_t ret = lws_write(wsi, (unsigned char *) wo->str->str + wo->str_done,
|
|
to_send, wo->protocol);
|
|
if (ret != to_send)
|
|
ilogs(http, LOG_ERR, "Invalid LWS write: %lu != %lu",
|
|
(unsigned long) ret,
|
|
(unsigned long) to_send);
|
|
wo->str_done += ret;
|
|
|
|
if (wo->protocol == LWS_WRITE_HTTP)
|
|
is_http = 1;
|
|
}
|
|
|
|
next:
|
|
websocket_output_free(wo);
|
|
}
|
|
g_queue_push_tail(&wc->output_q, websocket_output_new());
|
|
|
|
mutex_unlock(&wc->lock);
|
|
|
|
int ret = 0;
|
|
if (is_http)
|
|
if (lws_http_transaction_completed(wsi) == 1) // may destroy `wc`
|
|
ret = -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void websocket_http_response(struct websocket_conn *wc, int status, const char *content_type,
|
|
ssize_t content_length)
|
|
{
|
|
LOCK(&wc->lock);
|
|
|
|
struct websocket_output *wo = g_queue_peek_tail(&wc->output_q);
|
|
|
|
wo->http_status = status;
|
|
wo->content_type = content_type;
|
|
wo->content_length = content_length;
|
|
}
|
|
void websocket_http_complete(struct websocket_conn *wc, int status, const char *content_type,
|
|
ssize_t content_length, const char *content)
|
|
{
|
|
websocket_http_response(wc, status, content_type, content_length);
|
|
websocket_write_http(wc, content, true);
|
|
}
|
|
|
|
|
|
static const char *websocket_http_ping(struct websocket_message *wm) {
|
|
ilogs(http, LOG_DEBUG, "Respoding to GET /ping");
|
|
websocket_http_complete(wm->wc, 200, "text/plain", 5, "pong\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const char *websocket_http_metrics(struct websocket_message *wm) {
|
|
ilogs(http, LOG_DEBUG, "Respoding to GET /metrics");
|
|
|
|
AUTO_CLEANUP_INIT(GQueue *metrics, statistics_free_metrics, statistics_gather_metrics(NULL));
|
|
AUTO_CLEANUP_INIT(GString *outp, __g_string_free, g_string_new(""));
|
|
AUTO_CLEANUP_INIT(GHashTable *metric_types, __g_hash_table_destroy,
|
|
g_hash_table_new(g_str_hash, g_str_equal));
|
|
|
|
for (GList *l = metrics->head; l; l = l->next) {
|
|
struct stats_metric *m = l->data;
|
|
if (!m->label)
|
|
continue;
|
|
if (!m->value_short)
|
|
continue;
|
|
if (!m->prom_name)
|
|
continue;
|
|
|
|
if (!g_hash_table_lookup(metric_types, m->prom_name)) {
|
|
if (m->descr)
|
|
g_string_append_printf(outp, "# HELP rtpengine_%s %s\n",
|
|
m->prom_name, m->descr);
|
|
if (m->prom_type)
|
|
g_string_append_printf(outp, "# TYPE rtpengine_%s %s\n",
|
|
m->prom_name, m->prom_type);
|
|
g_hash_table_insert(metric_types, (void *) m->prom_name, (void *) 0x1);
|
|
}
|
|
|
|
g_string_append_printf(outp, "rtpengine_%s", m->prom_name);
|
|
if (m->prom_label)
|
|
g_string_append_printf(outp, "{%s}", m->prom_label);
|
|
g_string_append_printf(outp, " %s\n", m->value_short);
|
|
}
|
|
|
|
websocket_http_complete(wm->wc, 200, "text/plain", outp->len, outp->str);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// adds printf string to output buffer without triggering response
|
|
static void websocket_queue_printf(struct cli_writer *cw, const char *fmt, ...) {
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
char *s = g_strdup_vprintf(fmt, va);
|
|
va_end(va);
|
|
websocket_queue_raw(cw->ptr, s, strlen(s));
|
|
g_free(s);
|
|
}
|
|
|
|
|
|
static const char *websocket_http_cli(struct websocket_message *wm) {
|
|
assert(strncmp(wm->uri, "/cli/", 5) == 0);
|
|
char *uri = wm->uri+5;
|
|
|
|
ilogs(http, LOG_DEBUG, "Respoding to GET /cli/%s", uri);
|
|
|
|
str uri_cmd = STR_INIT(uri);
|
|
|
|
struct cli_writer cw = {
|
|
.cw_printf = websocket_queue_printf,
|
|
.ptr = wm->wc,
|
|
};
|
|
cli_handle(&uri_cmd, &cw);
|
|
|
|
size_t len = websocket_queue_len(wm->wc);
|
|
|
|
websocket_http_complete(wm->wc, 200, "text/plain", len, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const char *websocket_cli_process(struct websocket_message *wm) {
|
|
ilogs(http, LOG_DEBUG, "Processing websocket CLI req '%s'", wm->body->str);
|
|
|
|
str uri_cmd = STR_INIT_LEN(wm->body->str, wm->body->len);
|
|
|
|
struct cli_writer cw = {
|
|
.cw_printf = websocket_queue_printf,
|
|
.ptr = wm->wc,
|
|
};
|
|
cli_handle(&uri_cmd, &cw);
|
|
|
|
websocket_write_binary(wm->wc, NULL, 0, true);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void websocket_ng_send_ws(str *cookie, str *body, const endpoint_t *sin, const sockaddr_t *from,
|
|
void *p1)
|
|
{
|
|
struct websocket_conn *wc = p1;
|
|
websocket_queue_raw(wc, cookie->s, cookie->len);
|
|
websocket_queue_raw(wc, " ", 1);
|
|
websocket_queue_raw(wc, body->s, body->len);
|
|
websocket_write_binary(wc, NULL, 0, true);
|
|
}
|
|
static void websocket_ng_send_http(str *cookie, str *body, const endpoint_t *sin, const sockaddr_t *from,
|
|
void *p1)
|
|
{
|
|
struct websocket_conn *wc = p1;
|
|
websocket_http_response(wc, 200, "application/x-rtpengine-ng", cookie->len + 1 + body->len);
|
|
websocket_queue_raw(wc, cookie->s, cookie->len);
|
|
websocket_queue_raw(wc, " ", 1);
|
|
websocket_queue_raw(wc, body->s, body->len);
|
|
websocket_write_http(wc, NULL, true);
|
|
}
|
|
|
|
static void __ng_buf_free(void *p) {
|
|
struct websocket_ng_buf *buf = p;
|
|
g_string_free(buf->body, TRUE);
|
|
}
|
|
|
|
static const char *websocket_ng_process(struct websocket_message *wm) {
|
|
struct websocket_ng_buf *buf = obj_alloc0("websocket_ng_buf", sizeof(*buf), __ng_buf_free);
|
|
|
|
endpoint_print(&wm->wc->endpoint, buf->addr, sizeof(buf->addr));
|
|
|
|
ilogs(http, LOG_DEBUG, "Processing websocket NG req from %s", buf->addr);
|
|
|
|
// steal body and initialise
|
|
buf->body = wm->body;
|
|
wm->body = g_string_new("");
|
|
str_init_len(&buf->cmd, buf->body->str, buf->body->len);
|
|
buf->endpoint = wm->wc->endpoint;
|
|
|
|
control_ng_process(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_ws, wm->wc, &buf->obj);
|
|
|
|
obj_put(buf);
|
|
|
|
return NULL;
|
|
}
|
|
static const char *websocket_http_ng(struct websocket_message *wm) {
|
|
struct websocket_ng_buf *buf = obj_alloc0("websocket_ng_buf", sizeof(*buf), __ng_buf_free);
|
|
|
|
endpoint_print(&wm->wc->endpoint, buf->addr, sizeof(buf->addr));
|
|
|
|
ilogs(http, LOG_DEBUG, "Respoding to POST /ng from %s", buf->addr);
|
|
|
|
// steal body and initialise
|
|
buf->body = wm->body;
|
|
wm->body = g_string_new("");
|
|
str_init_len(&buf->cmd, buf->body->str, buf->body->len);
|
|
buf->endpoint = wm->wc->endpoint;
|
|
|
|
if (control_ng_process(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_http, wm->wc,
|
|
&buf->obj))
|
|
websocket_http_complete(wm->wc, 600, "text/plain", 6, "error\n");
|
|
|
|
obj_put(buf);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *websocket_http_404(struct websocket_message *wm) {
|
|
ilogs(http, LOG_WARN, "Unhandled HTTP URI: '%s'", wm->uri);
|
|
websocket_http_complete(wm->wc, 404, "text/plain", 10, "not found\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static int websocket_http_get(struct websocket_conn *wc) {
|
|
struct websocket_message *wm = wc->wm;
|
|
const char *uri = wm->uri;
|
|
websocket_message_func_t handler = NULL;
|
|
|
|
ilogs(http, LOG_INFO, "HTTP GET from %s: '%s'", endpoint_print_buf(&wc->endpoint), uri);
|
|
wm->method = M_GET;
|
|
|
|
if (!strcmp(uri, "/ping"))
|
|
handler = websocket_http_ping;
|
|
else if (!strncmp(uri, "/cli/", 5))
|
|
handler = websocket_http_cli;
|
|
else if (!strcmp(uri, "/metrics"))
|
|
handler = websocket_http_metrics;
|
|
else if (!strncmp(uri, "/admin/", 7))
|
|
handler = websocket_janus_get;
|
|
else
|
|
handler = websocket_http_404;
|
|
|
|
websocket_message_push(wc, handler);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int websocket_http_post(struct websocket_conn *wc) {
|
|
struct websocket_message *wm = wc->wm;
|
|
|
|
ilogs(http, LOG_INFO, "HTTP POST from %s: '%s'", endpoint_print_buf(&wc->endpoint), wm->uri);
|
|
wm->method = M_POST;
|
|
|
|
char ct[64];
|
|
if (lws_hdr_total_length(wc->wsi, WSI_TOKEN_HTTP_CONTENT_TYPE) >= sizeof(ct)) {
|
|
ilogs(http, LOG_WARN, "Too long content-type header, rejecting HTTP POST");
|
|
return -1;
|
|
}
|
|
|
|
if (lws_hdr_copy(wc->wsi, ct, sizeof(ct)-1, WSI_TOKEN_HTTP_CONTENT_TYPE) <= 0) {
|
|
ilogs(http, LOG_WARN, "Failed to get Content-type header, rejecting HTTP POST");
|
|
return -1;
|
|
}
|
|
|
|
if (lws_hdr_total_length(wc->wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) <= 0) {
|
|
ilogs(http, LOG_WARN, "Failed to get Content-length header, rejecting HTTP POST");
|
|
return -1;
|
|
}
|
|
|
|
ilogs(http, LOG_DEBUG, "POST content-type: %s", ct);
|
|
|
|
if (!strcasecmp(ct, "application/json"))
|
|
wm->content_type = CT_JSON;
|
|
else if (!strcasecmp(ct, "application/x-rtpengine-ng"))
|
|
wm->content_type = CT_NG;
|
|
else
|
|
ilogs(http, LOG_WARN, "Unsupported content-type '%s'", ct);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const char *websocket_http_options_generic(struct websocket_message *wm) {
|
|
ilogs(http, LOG_DEBUG, "Respoding to OPTIONS");
|
|
websocket_http_complete(wm->wc, 200, NULL, 0, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int websocket_http_options(struct websocket_conn *wc) {
|
|
struct websocket_message *wm = wc->wm;
|
|
|
|
ilogs(http, LOG_INFO, "HTTP OPTIONS from %s: '%s'", endpoint_print_buf(&wc->endpoint), wm->uri);
|
|
wm->method = M_OPTIONS;
|
|
|
|
websocket_message_push(wc, websocket_http_options_generic);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int websocket_http_body(struct websocket_conn *wc, const char *body, size_t len) {
|
|
struct websocket_message *wm = wc->wm;
|
|
const char *uri = wm->uri;
|
|
websocket_message_func_t handler = NULL;
|
|
|
|
if (wm->method != M_POST) {
|
|
ilogs(http, LOG_WARN, "Rejecting HTTP body on unsupported method");
|
|
return -1;
|
|
}
|
|
|
|
if (len) {
|
|
ilogs(http, LOG_DEBUG, "HTTP body: %lu bytes", (unsigned long) len);
|
|
g_string_append_len(wm->body, body, len);
|
|
return 0;
|
|
}
|
|
|
|
ilogs(http, LOG_DEBUG, "HTTP body complete: '%.*s'", (int) wm->body->len, wm->body->str);
|
|
|
|
if (!strcmp(uri, "/ng") && wm->method == M_POST && wm->content_type == CT_NG)
|
|
handler = websocket_http_ng;
|
|
else if (!strcmp(uri, "/admin") && wm->method == M_POST && wm->content_type == CT_JSON)
|
|
handler = websocket_janus_process;
|
|
else if (!strcmp(uri, "/janus") && wm->method == M_POST && wm->content_type == CT_JSON)
|
|
handler = websocket_janus_process;
|
|
else if (!strncmp(uri, "/janus/", 7) && wm->method == M_POST && wm->content_type == CT_JSON)
|
|
handler = websocket_janus_post;
|
|
else
|
|
handler = websocket_http_404;
|
|
|
|
websocket_message_push(wc, handler);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void websocket_conn_cleanup(struct websocket_conn *wc) {
|
|
if (!wc)
|
|
return;
|
|
if (!wc->wsi) // not initialised
|
|
return;
|
|
|
|
// wait until all remaining tasks are finished
|
|
mutex_lock(&wc->lock);
|
|
while (wc->jobs)
|
|
cond_wait(&wc->cond, &wc->lock);
|
|
|
|
// lock order constraint: janus_session lock first, websocket_conn lock second:
|
|
// therefore, remove janus_sessions list from wc, then unlock, then iterate the
|
|
// list, as janus_detach_websocket locks the session
|
|
|
|
GHashTable *janus_sessions = wc->janus_sessions;
|
|
wc->janus_sessions = NULL;
|
|
|
|
mutex_unlock(&wc->lock);
|
|
|
|
// detach all Janus sessions
|
|
if (janus_sessions) {
|
|
GHashTableIter iter;
|
|
g_hash_table_iter_init(&iter, janus_sessions);
|
|
gpointer key;
|
|
while (g_hash_table_iter_next(&iter, &key, NULL)) {
|
|
janus_detach_websocket(key, wc);
|
|
obj_put_o(key);
|
|
}
|
|
g_hash_table_destroy(janus_sessions);
|
|
}
|
|
|
|
|
|
assert(wc->messages.length == 0);
|
|
|
|
g_string_free(wc->wm->body, TRUE);
|
|
if (wc->wm->uri)
|
|
free(wc->wm->uri);
|
|
g_slice_free1(sizeof(*wc->wm), wc->wm);
|
|
wc->wm = NULL;
|
|
g_queue_clear_full(&wc->output_q, websocket_output_free);
|
|
if (wc->uri)
|
|
free(wc->uri);
|
|
|
|
mutex_destroy(&wc->lock);
|
|
|
|
memset(wc, 0, sizeof(*wc));
|
|
}
|
|
|
|
|
|
static int websocket_conn_init(struct lws *wsi, void *p) {
|
|
struct websocket_conn *wc = p;
|
|
|
|
if (!wc)
|
|
return -1;
|
|
|
|
memset(wc, 0, sizeof(*wc));
|
|
|
|
struct sockaddr_storage sa = {0,};
|
|
socklen_t sl = sizeof(sa);
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
struct lws *network_wsi = lws_get_network_wsi(wsi);
|
|
int fd = lws_get_socket_fd(network_wsi);
|
|
if (fd == -1) {
|
|
// SSL?
|
|
SSL *ssl = lws_get_ssl(network_wsi);
|
|
if (ssl) {
|
|
fd = SSL_get_fd(ssl);
|
|
} else {
|
|
ilogs(http, LOG_ERR, "Failed to get socket for remote address of HTTP/WS connection");
|
|
return -1;
|
|
}
|
|
}
|
|
#else
|
|
int fd = lws_get_socket_fd(wsi);
|
|
#endif
|
|
|
|
if (getpeername(fd, (struct sockaddr *) &sa, &sl)) {
|
|
ilogs(http, LOG_ERR, "Failed to get remote address of HTTP/WS connection (fd %i): %s",
|
|
fd, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
endpoint_parse_sockaddr_storage(&wc->endpoint, &sa);
|
|
wc->wsi = wsi;
|
|
mutex_init(&wc->lock);
|
|
cond_init(&wc->cond);
|
|
g_queue_init(&wc->messages);
|
|
g_queue_push_tail(&wc->output_q, websocket_output_new());
|
|
wc->wm = websocket_message_new(wc);
|
|
wc->janus_sessions = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void websocket_conn_add_session(struct websocket_conn *wc, struct janus_session *s) {
|
|
mutex_lock(&wc->lock);
|
|
if (wc->janus_sessions) {
|
|
assert(g_hash_table_lookup(wc->janus_sessions, s) == NULL);
|
|
g_hash_table_insert(wc->janus_sessions, s, s);
|
|
}
|
|
mutex_unlock(&wc->lock);
|
|
}
|
|
|
|
|
|
static int websocket_do_http(struct lws *wsi, struct websocket_conn *wc, const char *uri) {
|
|
ilogs(http, LOG_DEBUG, "HTTP request start: %s", uri);
|
|
|
|
if (websocket_conn_init(wsi, wc) < 0)
|
|
return 0;
|
|
wc->wm->uri = strdup(uri);
|
|
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
|
|
return websocket_http_get(wc);
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
|
|
return websocket_http_post(wc);
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI))
|
|
return websocket_http_options(wc);
|
|
|
|
ilogs(http, LOG_INFO, "Ignoring HTTP request to %s with unsupported method", uri);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int websocket_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len)
|
|
{
|
|
ilogs(http, LOG_DEBUG, "http-only callback %i %p %p", reason, wsi, user);
|
|
|
|
gettimeofday(&rtpe_now, NULL);
|
|
|
|
switch (reason) {
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
|
case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
|
|
case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 4
|
|
case LWS_CALLBACK_HTTP_CONFIRM_UPGRADE:
|
|
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
|
#endif
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
case LWS_CALLBACK_ADD_HEADERS:
|
|
case LWS_CALLBACK_EVENT_WAIT_CANCELLED: // ?
|
|
#endif
|
|
break;
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
|
ilogs(http, LOG_DEBUG, "HTTP connection reset %p", wsi);
|
|
websocket_conn_cleanup(user);
|
|
break;
|
|
#endif
|
|
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
|
|
return -1; // disallow non supported websocket protocols
|
|
case LWS_CALLBACK_GET_THREAD_ID:
|
|
return (long int) pthread_self();
|
|
case LWS_CALLBACK_WSI_CREATE:
|
|
ilogs(http, LOG_DEBUG, "WS client created %p", wsi);
|
|
break;
|
|
case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
|
|
ilogs(http, LOG_DEBUG, "New WS client %p", wsi);
|
|
break;
|
|
case LWS_CALLBACK_HTTP:
|
|
return websocket_do_http(wsi, user, in);
|
|
case LWS_CALLBACK_HTTP_BODY:
|
|
if (len == 0)
|
|
return 0;
|
|
return websocket_http_body(user, in, len);
|
|
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
|
return websocket_http_body(user, NULL, 0);
|
|
case LWS_CALLBACK_CLOSED_HTTP:
|
|
ilogs(http, LOG_DEBUG, "HTTP connection closed %p", wsi);
|
|
websocket_conn_cleanup(user);
|
|
break;
|
|
case LWS_CALLBACK_WSI_DESTROY:
|
|
ilogs(http, LOG_DEBUG, "WS client destroyed %p", wsi);
|
|
break;
|
|
case LWS_CALLBACK_ESTABLISHED:
|
|
case LWS_CALLBACK_RECEIVE:
|
|
case LWS_CALLBACK_CLOSED:
|
|
ilogs(http, LOG_WARN, "Invalid HTTP callback %i", reason);
|
|
return -1;
|
|
case LWS_CALLBACK_HTTP_WRITEABLE:
|
|
return websocket_dequeue(user);
|
|
default:
|
|
ilogs(http, LOG_DEBUG, "Unhandled HTTP callback %i", reason);
|
|
break;
|
|
}
|
|
|
|
release_closed_sockets();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int websocket_protocol(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len, websocket_message_func_t handler_func, const char *name)
|
|
{
|
|
struct websocket_conn *wc = user;
|
|
|
|
ilogs(http, LOG_DEBUG, "Websocket protocol '%s' callback %i %p %p", name, reason, wsi, wc);
|
|
|
|
gettimeofday(&rtpe_now, NULL);
|
|
|
|
switch (reason) {
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
|
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 4
|
|
case LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL:
|
|
case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL:
|
|
#endif
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
case LWS_CALLBACK_ADD_HEADERS:
|
|
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
|
case LWS_CALLBACK_EVENT_WAIT_CANCELLED: // ?
|
|
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
|
|
#endif
|
|
break;
|
|
case LWS_CALLBACK_GET_THREAD_ID:
|
|
return (long int) pthread_self();
|
|
case LWS_CALLBACK_ESTABLISHED:
|
|
ilogs(http, LOG_DEBUG, "Websocket protocol '%s' established", name);
|
|
if (websocket_conn_init(wsi, wc) < 0)
|
|
break;
|
|
int get_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
|
|
if (get_len > 0) {
|
|
wc->uri = malloc(get_len + 1);
|
|
if (wc->uri) {
|
|
if (lws_hdr_copy(wsi, wc->uri, get_len + 1, WSI_TOKEN_GET_URI) <= 0) {
|
|
free(wc->uri);
|
|
wc->uri = NULL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case LWS_CALLBACK_CLOSED:
|
|
ilogs(http, LOG_DEBUG, "Websocket protocol '%s' closed", name);
|
|
websocket_conn_cleanup(wc);
|
|
ilogs(http, LOG_DEBUG, "Websocket protocol '%s' ready for cleanup", name);
|
|
break;
|
|
case LWS_CALLBACK_RECEIVE:
|
|
ilogs(http, LOG_DEBUG, "Websocket protocol '%s' data (final %i, remain %zu) "
|
|
"received for '%s': '%.*s'",
|
|
name,
|
|
lws_is_final_fragment(wsi),
|
|
lws_remaining_packet_payload(wsi),
|
|
wc->uri, (int) len, (const char *) in);
|
|
wc->wm->method = M_WEBSOCKET;
|
|
g_string_append_len(wc->wm->body, in, len);
|
|
if (lws_is_final_fragment(wsi))
|
|
websocket_message_push(wc, handler_func);
|
|
break;
|
|
case LWS_CALLBACK_SERVER_WRITEABLE:
|
|
return websocket_dequeue(user);
|
|
default:
|
|
ilogs(http, LOG_DEBUG, "Unhandled websocket protocol '%s' callback %i", name, reason);
|
|
break;
|
|
}
|
|
|
|
release_closed_sockets();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int websocket_janus(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len)
|
|
{
|
|
return websocket_protocol(wsi, reason, user, in, len, websocket_janus_process, "janus-protocol");
|
|
}
|
|
static int websocket_rtpengine_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len)
|
|
{
|
|
return websocket_protocol(wsi, reason, user, in, len, websocket_echo_process, "echo.rtpengine.com");
|
|
}
|
|
static int websocket_rtpengine_cli(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len)
|
|
{
|
|
return websocket_protocol(wsi, reason, user, in, len, websocket_cli_process, "rtpengine-cli");
|
|
}
|
|
static int websocket_rtpengine_ng(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
size_t len)
|
|
{
|
|
return websocket_protocol(wsi, reason, user, in, len, websocket_ng_process, "rtpengine-ng");
|
|
}
|
|
|
|
|
|
static const struct lws_protocols websocket_protocols[] = {
|
|
{
|
|
.name = "http-only",
|
|
.callback = websocket_http,
|
|
.per_session_data_size = sizeof(struct websocket_conn),
|
|
},
|
|
{
|
|
.name = "janus-protocol",
|
|
.callback = websocket_janus,
|
|
.per_session_data_size = sizeof(struct websocket_conn),
|
|
},
|
|
{
|
|
.name = "echo.rtpengine.com",
|
|
.callback = websocket_rtpengine_echo,
|
|
.per_session_data_size = sizeof(struct websocket_conn),
|
|
},
|
|
{
|
|
.name = "cli.rtpengine.com",
|
|
.callback = websocket_rtpengine_cli,
|
|
.per_session_data_size = sizeof(struct websocket_conn),
|
|
},
|
|
{
|
|
.name = "ng.rtpengine.com",
|
|
.callback = websocket_rtpengine_ng,
|
|
.per_session_data_size = sizeof(struct websocket_conn),
|
|
},
|
|
{ 0, }
|
|
};
|
|
|
|
|
|
static void websocket_log(int level, const char *line) {
|
|
if ((level & LLL_ERR))
|
|
level = LOG_ERR;
|
|
else if ((level & LLL_WARN))
|
|
level = LOG_WARN;
|
|
else if ((level & LLL_NOTICE))
|
|
level = LOG_NOTICE;
|
|
else if ((level & LLL_INFO))
|
|
level = LOG_INFO;
|
|
else
|
|
level = LOG_DEBUG;
|
|
ilogs(http, level, "libwebsockets: %s", line);
|
|
}
|
|
|
|
|
|
static void websocket_cleanup(void) {
|
|
if (websocket_context)
|
|
lws_context_destroy(websocket_context);
|
|
websocket_context = NULL;
|
|
if (websocket_threads)
|
|
g_thread_pool_free(websocket_threads, TRUE, TRUE);
|
|
websocket_threads = NULL;
|
|
|
|
while (websocket_vhost_configs.length) {
|
|
struct lws_context_creation_info *vhost = g_queue_pop_head(&websocket_vhost_configs);
|
|
g_free((void *) vhost->iface);
|
|
g_slice_free1(sizeof(*vhost), vhost);
|
|
}
|
|
}
|
|
|
|
|
|
static void addr_any_v6_consolidate(endpoint_t eps[2], bool have_lws_ipv6) {
|
|
// Don't try to double bind on ADDR_ANY. If we find ADDR_ANY, bind to the
|
|
// v6 port and omit the v4 binding (and let libwebsockets handle the 4/6
|
|
// translation) unless we find ourselves without v6 support.
|
|
|
|
if (!eps[1].port)
|
|
return;
|
|
if (!have_lws_ipv6)
|
|
return;
|
|
if (eps[0].address.family->af == AF_INET6)
|
|
return;
|
|
if (!is_addr_unspecified(&eps[0].address))
|
|
return;
|
|
|
|
// The only case that needs handling: ADDR_ANY requested, v6 support is
|
|
// available, and v6 binding is given second.
|
|
|
|
eps[0] = eps[1];
|
|
eps[1].port = 0;
|
|
}
|
|
|
|
int websocket_init(void) {
|
|
assert(websocket_context == NULL);
|
|
|
|
if ((!rtpe_config.http_ifs || !*rtpe_config.http_ifs) &&
|
|
(!rtpe_config.https_ifs || !*rtpe_config.https_ifs))
|
|
return 0;
|
|
|
|
struct sockaddr_storage sa;
|
|
int ret = lws_interface_to_sa(1, "::1", (void *) &sa, sizeof(sa));
|
|
bool have_lws_ipv6 = (ret == 0);
|
|
|
|
const char *err = NULL;
|
|
|
|
lws_set_log_level(LLL_ERR | LLL_WARN, websocket_log);
|
|
|
|
struct lws_context_creation_info wci = {
|
|
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
|
|
#if LWS_LIBRARY_VERSION_MAJOR >= 4
|
|
LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND |
|
|
#endif
|
|
0,
|
|
};
|
|
websocket_context = lws_create_context(&wci);
|
|
err = "Failed to create LWS context";
|
|
if (!websocket_context)
|
|
goto err;
|
|
|
|
for (char **ifp = rtpe_config.http_ifs; ifp && *ifp; ifp++) {
|
|
char *ifa = *ifp;
|
|
ilogs(http, LOG_DEBUG, "Starting HTTP/WS '%s'", ifa);
|
|
endpoint_t eps[2];
|
|
err = "Failed to parse address/port";
|
|
if (endpoint_parse_any_getaddrinfo_alt(&eps[0], &eps[1], ifa))
|
|
goto err;
|
|
addr_any_v6_consolidate(eps, have_lws_ipv6);
|
|
|
|
bool success = false;
|
|
bool ipv6_fail = false;
|
|
for (int i = 0; i < G_N_ELEMENTS(eps); i++) {
|
|
endpoint_t *ep = &eps[i];
|
|
if (!ep->port)
|
|
continue;
|
|
if (ep->address.family->af == AF_INET6 && !have_lws_ipv6) {
|
|
ipv6_fail = true;
|
|
continue;
|
|
}
|
|
struct lws_context_creation_info *vhost = g_slice_alloc(sizeof(*vhost));
|
|
g_queue_push_tail(&websocket_vhost_configs, vhost);
|
|
|
|
*vhost = (struct lws_context_creation_info) {
|
|
.port = ep->port,
|
|
.iface = g_strdup(sockaddr_print_buf(&ep->address)),
|
|
.protocols = websocket_protocols,
|
|
};
|
|
vhost->vhost_name = vhost->iface;
|
|
if (ep->address.family->af == AF_INET)
|
|
vhost->options |= LWS_SERVER_OPTION_DISABLE_IPV6;
|
|
err = "LWS failed to create vhost";
|
|
if (!lws_create_vhost(websocket_context, vhost))
|
|
goto err;
|
|
success = true;
|
|
}
|
|
err = "Failed to create any LWS vhost from given config";
|
|
if (ipv6_fail)
|
|
err = "Failed to create any LWS vhost from given config. Hint: LWS IPv6 support is not "
|
|
"available and config lists at least one IPv6 vhost";
|
|
if (!success)
|
|
goto err;
|
|
}
|
|
|
|
for (char **ifp = rtpe_config.https_ifs; ifp && *ifp; ifp++) {
|
|
err = "HTTPS/WSS listener requested, but no certificate given";
|
|
if (!rtpe_config.https_cert)
|
|
goto err;
|
|
|
|
char *ifa = *ifp;
|
|
ilogs(http, LOG_DEBUG, "Starting HTTPS/WSS '%s'", ifa);
|
|
endpoint_t eps[2];
|
|
err = "Failed to parse address/port";
|
|
if (endpoint_parse_any_getaddrinfo_alt(&eps[0], &eps[1], ifa))
|
|
goto err;
|
|
addr_any_v6_consolidate(eps, have_lws_ipv6);
|
|
|
|
bool success = false;
|
|
bool ipv6_fail = false;
|
|
for (int i = 0; i < G_N_ELEMENTS(eps); i++) {
|
|
endpoint_t *ep = &eps[i];
|
|
if (!ep->port)
|
|
continue;
|
|
if (ep->address.family->af == AF_INET6 && !have_lws_ipv6) {
|
|
ipv6_fail = true;
|
|
continue;
|
|
}
|
|
struct lws_context_creation_info *vhost = g_slice_alloc(sizeof(*vhost));
|
|
g_queue_push_tail(&websocket_vhost_configs, vhost);
|
|
|
|
*vhost = (struct lws_context_creation_info) {
|
|
.port = ep->port,
|
|
.iface = g_strdup(sockaddr_print_buf(&ep->address)),
|
|
.protocols = websocket_protocols,
|
|
.ssl_cert_filepath = rtpe_config.https_cert,
|
|
.ssl_private_key_filepath = rtpe_config.https_key ? : rtpe_config.https_cert,
|
|
.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,
|
|
// XXX cipher list, key password
|
|
};
|
|
vhost->vhost_name = vhost->iface;
|
|
if (ep->address.family->af == AF_INET)
|
|
vhost->options |= LWS_SERVER_OPTION_DISABLE_IPV6;
|
|
err = "LWS failed to create vhost";
|
|
if (!lws_create_vhost(websocket_context, vhost))
|
|
goto err;
|
|
success = true;
|
|
}
|
|
err = "Failed to create any LWS vhost from given config";
|
|
if (ipv6_fail)
|
|
err = "Failed to create any LWS vhost from given config. Hint: LWS IPv6 support is not "
|
|
"available and config lists at least one IPv6 vhost";
|
|
if (!success)
|
|
goto err;
|
|
}
|
|
|
|
int num_threads = rtpe_config.http_threads ? : rtpe_config.num_threads;
|
|
websocket_threads = g_thread_pool_new(websocket_process, NULL, num_threads, FALSE, NULL);
|
|
|
|
ilogs(http, LOG_DEBUG, "Websocket init complete with %i threads", num_threads);
|
|
return 0;
|
|
|
|
err:
|
|
ilogs(http, LOG_ERROR, "Failed to start websocket listener: %s", err);
|
|
websocket_cleanup();
|
|
return -1;
|
|
}
|
|
|
|
static void websocket_loop(void *p) {
|
|
ilogs(http, LOG_INFO, "Websocket listener thread running");
|
|
while (!rtpe_shutdown) {
|
|
// see websocket_write_raw() for locking logic
|
|
|
|
mutex_lock(&websocket_service_lock);
|
|
lws_service(websocket_context, 100);
|
|
mutex_unlock(&websocket_service_lock);
|
|
|
|
mutex_lock(&websocket_callback_lock);
|
|
mutex_unlock(&websocket_callback_lock);
|
|
}
|
|
|
|
websocket_cleanup();
|
|
}
|
|
|
|
void websocket_start(void) {
|
|
if (!websocket_context)
|
|
return;
|
|
thread_create_detach_prio(websocket_loop, NULL, rtpe_config.scheduling, rtpe_config.priority, "websocket");
|
|
}
|
|
void websocket_stop(void) {
|
|
if (!websocket_context)
|
|
return;
|
|
lws_cancel_service(websocket_context);
|
|
}
|