0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-25 05:31:37 +00:00
netdata_netdata/web/rtc/webrtc.c
Costa Tsaousis c3d70ffcb4
WEBRTC for communication between agents and browsers ()
* initial webrtc setup

* missing files

* rewrite of webrtc integration

* initialization and cleanup of webrtc connections

* make it compile without libdatachannel

* add missing webrtc_initialize() function when webrtc is not enabled

* make c++17 optional

* add build/m4/ax_compiler_vendor.m4

* add ax_cxx_compile_stdcxx.m4

* added new m4 files to makefile.am

* id all webrtc connections

* show warning when webrtc is disabled

* fixed message

* moved all webrtc error checking inside webrtc.cpp

* working webrtc connection establishment and cleanup

* remove obsolete code

* rewrote webrtc code in C to remove dependency for c++17

* fixed left-over reference

* detect binary and text messages

* minor fix

* naming of webrtc threads

* added webrtc configuration

* fix for thread_get_name_np()

* smaller web_client memory footprint

* universal web clients cache

* free web clients every 100 uses

* webrtc is now enabled by default only when compiled with internal checks

* webrtc responses to /api/ requests, including LZ4 compression

* fix for binary and text messages

* web_client_cache is now global

* unification of the internal web server API, for web requests, aclk request, webrtc requests

* more cleanup and unification of web client timings

* fixed compiler warnings

* update sent and received bytes

* eliminated of almost all big buffers in web client

* registry now uses the new json generation

* cookies are now an array; fixed redirects

* fix redirects, again

* write cookies directly to the header buffer, eliminating the need for cookie structures in web client

* reset the has_cookies flag

* gathered all web client cleanup to one function

* fixes redirects

* added summary.globals in /api/v2/data response

* ars to arc in /api/v2/data

* properly handle host impersonation

* set the context of mem.numa_nodes
2023-04-20 20:49:06 +03:00

740 lines
26 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
#include "webrtc.h"
#include "../server/web_client.h"
#include "../server/web_client_cache.h"
#ifdef HAVE_LIBDATACHANNEL
#include "rtc/rtc.h"
#define WEBRTC_OUR_MAX_MESSAGE_SIZE (5 * 1024 * 1024)
#define WEBRTC_DEFAULT_REMOTE_MAX_MESSAGE_SIZE (65536)
#define WEBRTC_COMPRESSED_HEADER_SIZE 200
static void webrtc_log(rtcLogLevel level, const char *message) {
switch(level) {
case RTC_LOG_NONE:
break;
case RTC_LOG_WARNING:
case RTC_LOG_ERROR:
case RTC_LOG_FATAL:
error("WEBRTC: %s", message);
break;
case RTC_LOG_INFO:
info("WEBRTC: %s", message);
break;
default:
case RTC_LOG_DEBUG:
case RTC_LOG_VERBOSE:
internal_error(true, "WEBRTC: %s", message);
break;
}
}
typedef struct webrtc_datachannel {
int dc;
char *label;
struct webrtc_connection *conn;
bool open; // atomic
struct {
struct webrtc_datachannel *prev;
struct webrtc_datachannel *next;
} link;
} WEBRTC_DC;
typedef struct webrtc_connection {
int pc;
rtcConfiguration config;
rtcState state;
rtcGatheringState gathering_state;
size_t max_message_size;
size_t local_max_message_size;
size_t remote_max_message_size;
struct {
SPINLOCK spinlock;
BUFFER *wb;
bool sdp;
bool candidates;
} response;
struct {
SPINLOCK spinlock;
WEBRTC_DC *head;
} channels;
struct {
struct webrtc_connection *prev;
struct webrtc_connection *next;
} link;
} WEBRTC_CONN;
#define WEBRTC_MAX_ICE_SERVERS 100
static struct {
bool enabled;
char *iceServers[WEBRTC_MAX_ICE_SERVERS];
int iceServersCount;
char *proxyServer;
char *bindAddress;
struct {
SPINLOCK spinlock;
WEBRTC_CONN *head;
} unsafe;
} webrtc_base = {
#ifdef NETDATA_INTERNAL_CHECKS
.enabled = true,
#else
.enabled = false,
#endif
.iceServers = {
// Format:
// [("stun"|"turn"|"turns") (":"|"://")][username ":" password "@"]hostname[":" port]["?transport=" ("udp"|"tcp"|"tls")]
//
// Note transports TCP and TLS are only available for a TURN server with libnice as ICE backend and govern only the
// TURN control connection, meaning relaying is always performed over UDP.
//
// If the username or password of a URI contains reserved special characters, they must be percent-encoded.
// In particular, ":" must be encoded as "%3A" and "@" must by encoded as "%40".
"stun://stun.l.google.com:19302",
NULL, // terminator
},
.iceServersCount = 1,
.proxyServer = NULL, // [("http"|"socks5") (":"|"://")][username ":" password "@"]hostname[" :" port]
.bindAddress = NULL,
.unsafe = {
.spinlock = NETDATA_SPINLOCK_INITIALIZER,
.head = NULL,
},
};
static inline bool webrtc_dc_is_open(WEBRTC_DC *chan) {
return __atomic_load_n(&chan->open, __ATOMIC_RELAXED);
}
static void webrtc_config_ice_servers(void) {
BUFFER *wb = buffer_create(0, NULL);
int i;
for(i = 0; i < WEBRTC_MAX_ICE_SERVERS ;i++) {
if (webrtc_base.iceServers[i]) {
if (buffer_strlen(wb))
buffer_strcat(wb, " ");
internal_error(true, "WEBRTC: default ice server No %d is: '%s'", i, webrtc_base.iceServers[i]);
buffer_strcat(wb, webrtc_base.iceServers[i]);
}
else
break;
}
webrtc_base.iceServersCount = i;
internal_error(true, "WEBRTC: there are %d default ice servers: '%s'", webrtc_base.iceServersCount, buffer_tostring(wb));
char *servers = config_get(CONFIG_SECTION_WEBRTC, "ice servers", buffer_tostring(wb));
webrtc_base.iceServersCount = 0;
char *s = servers, *e;
while(*s) {
if(isspace(*s))
s++;
e = s;
while(*e && !isspace(*e))
e++;
if(s != e && webrtc_base.iceServersCount < WEBRTC_MAX_ICE_SERVERS) {
char old = *e;
*e = '\0';
internal_error(true, "WEBRTC: ice server No %d is: '%s'", webrtc_base.iceServersCount, s);
webrtc_base.iceServers[webrtc_base.iceServersCount++] = strdupz(s);
*e = old;
}
if(*e)
s = e + 1;
else
break;
}
buffer_free(wb);
}
void webrtc_initialize() {
webrtc_base.enabled = config_get_boolean(CONFIG_SECTION_WEBRTC, "enabled", webrtc_base.enabled);
internal_error(true, "WEBRTC: is %s", webrtc_base.enabled ? "enabled" : "disabled");
webrtc_config_ice_servers();
webrtc_base.proxyServer = config_get(CONFIG_SECTION_WEBRTC, "proxy server", webrtc_base.proxyServer ? webrtc_base.proxyServer : "");
if(!webrtc_base.proxyServer || !*webrtc_base.proxyServer)
webrtc_base.proxyServer = NULL;
internal_error(true, "WEBRTC: proxy server is: '%s'", webrtc_base.proxyServer ? webrtc_base.proxyServer : "");
webrtc_base.bindAddress = config_get(CONFIG_SECTION_WEBRTC, "bind address", webrtc_base.bindAddress ? webrtc_base.bindAddress : "");
if(!webrtc_base.bindAddress || !*webrtc_base.bindAddress)
webrtc_base.bindAddress = NULL;
internal_error(true, "WEBRTC: bind address is: '%s'", webrtc_base.bindAddress ? webrtc_base.bindAddress : "");
if(!webrtc_base.enabled)
return;
rtcLogLevel level;
#ifdef NETDATA_INTERNAL_CHECKS
level = RTC_LOG_INFO;
#else
level = RTC_LOG_WARNING;
#endif
rtcInitLogger(level, webrtc_log);
rtcPreload();
}
void webrtc_close_all_connections() {
if(!webrtc_base.enabled)
return;
rtcCleanup();
}
size_t find_max_message_size_in_sdp(const char *sdp) {
char *s = strstr(sdp, "a=max-message-size:");
if(s)
return str2ul(&s[19]);
return WEBRTC_DEFAULT_REMOTE_MAX_MESSAGE_SIZE;
}
// ----------------------------------------------------------------------------
// execute web API requests
static bool web_client_stop_callback(struct web_client *w __maybe_unused, void *data) {
WEBRTC_DC *chan = data;
return !webrtc_dc_is_open(chan);
}
static size_t webrtc_send_in_chunks(WEBRTC_DC *chan, const char *data, size_t size, int code, const char *message_type, HTTP_CONTENT_TYPE content_type, size_t max_message_size, bool binary) {
size_t sent_bytes = 0;
size_t chunk = 0;
size_t total_chunks = size / max_message_size;
if(total_chunks * max_message_size < size)
total_chunks++;
char *send_buffer = mallocz(chan->conn->max_message_size);
char *s = (char *)data;
size_t remaining = size;
while(remaining > 0) {
chunk++;
size_t message_size = MIN(remaining, max_message_size);
int len = snprintfz(send_buffer, WEBRTC_COMPRESSED_HEADER_SIZE, "%d %s %zu %zu %zu %s\r\n",
code,
message_type,
message_size,
chunk,
total_chunks,
web_content_type_to_string(content_type)
);
internal_fatal((size_t)len != strlen(send_buffer), "WEBRTC compressed header line mismatch");
internal_fatal(len + message_size > chan->conn->max_message_size, "WEBRTC message exceeds max message size");
memcpy(&send_buffer[len], s, message_size);
int total_message_size = (int)(len + message_size);
sent_bytes += total_message_size;
if(!binary)
total_message_size = -total_message_size;
if(rtcSendMessage(chan->dc, send_buffer, total_message_size) != RTC_ERR_SUCCESS)
error("WEBRTC[%d],DC[%d]: failed to send LZ4 chunk %zu of %zu", chan->conn->pc, chan->dc, chunk, total_chunks);
else
internal_error(true, "WEBRTC[%d],DC[%d]: sent chunk %zu of %zu, size %zu (total %d)",
chan->conn->pc, chan->dc, chunk, total_chunks, message_size, total_message_size);
s = s + message_size;
remaining -= message_size;
}
internal_fatal(chunk != total_chunks, "WEBRTC number of compressed chunks mismatch");
freez(send_buffer);
return sent_bytes;
}
static void webrtc_execute_api_request(WEBRTC_DC *chan, const char *request, size_t size __maybe_unused, bool binary __maybe_unused) {
struct timeval tv;
internal_error(true, "WEBRTC[%d],DC[%d]: got request '%s' of size %zu and type %s.",
chan->conn->pc, chan->dc, request, size, binary?"binary":"text");
struct web_client *w = web_client_get_from_cache();
w->statistics.received_bytes = size;
w->interrupt.callback = web_client_stop_callback;
w->interrupt.callback_data = chan;
w->acl = WEB_CLIENT_ACL_WEBRTC;
char *path = (char *)request;
if(strncmp(request, "POST ", 5) == 0) {
w->mode = WEB_CLIENT_MODE_POST;
path += 10;
}
else if(strncmp(request, "GET ", 4) == 0) {
w->mode = WEB_CLIENT_MODE_GET;
path += 4;
}
web_client_timeout_checkpoint_set(w, 0);
web_client_decode_path_and_query_string(w, path);
path = (char *)buffer_tostring(w->url_path_decoded);
w->response.code = web_client_api_request_with_node_selection(localhost, w, path);
web_client_timeout_checkpoint_response_ready(w, NULL);
size_t sent_bytes = 0;
size_t response_size = buffer_strlen(w->response.data);
bool send_plain = true;
int max_message_size = (int)chan->conn->max_message_size - WEBRTC_COMPRESSED_HEADER_SIZE;
if(!webrtc_dc_is_open(chan)) {
internal_error(true, "WEBRTC[%d],DC[%d]: ignoring API response on closed data channel.", chan->conn->pc, chan->dc);
goto cleanup;
}
else {
internal_error(true, "WEBRTC[%d],DC[%d]: prepared response with code %d, size %zu.",
chan->conn->pc, chan->dc, w->response.code, response_size);
}
#if defined(ENABLE_COMPRESSION)
int max_compressed_size = LZ4_compressBound((int)response_size);
char *compressed = mallocz(max_compressed_size);
int compressed_size = LZ4_compress_default(buffer_tostring(w->response.data), compressed,
(int)response_size, max_compressed_size);
if(compressed_size > 0) {
send_plain = false;
sent_bytes = webrtc_send_in_chunks(chan, compressed, compressed_size,
w->response.code, "LZ4", w->response.data->content_type,
max_message_size, true);
}
freez(compressed);
#endif
if(send_plain)
sent_bytes = webrtc_send_in_chunks(chan, buffer_tostring(w->response.data), buffer_strlen(w->response.data),
w->response.code, "PLAIN", w->response.data->content_type,
max_message_size, false);
w->statistics.sent_bytes = sent_bytes;
cleanup:
now_monotonic_high_precision_timeval(&tv);
log_access("%llu: %d '[RTC]:%d:%d' '%s' (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %d '%s'",
w->id
, gettid()
, chan->conn->pc, chan->dc
, "DATA"
, sent_bytes
, response_size
, response_size > sent_bytes ? -(((double)(response_size - sent_bytes) / (double)response_size) * 100.0) : ((response_size > 0) ? (((sent_bytes - response_size) / (double)response_size) * 100.0) : 0.0)
, dt_usec(&w->timings.tv_ready, &w->timings.tv_in) / 1000.0
, dt_usec(&tv, &w->timings.tv_ready) / 1000.0
, dt_usec(&tv, &w->timings.tv_in) / 1000.0
, w->response.code
, strip_control_characters((char *)buffer_tostring(w->url_as_received))
);
web_client_release_to_cache(w);
}
// ----------------------------------------------------------------------------
// webrtc data channel
static void myOpenCallback(int id, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_DC *chan = user_ptr;
internal_fatal(chan->dc != id, "WEBRTC[%d],DC[%d]: dc mismatch, expected %d, got %d", chan->conn->pc, chan->dc, chan->dc, id);
log_access("WEBRTC[%d],DC[%d]: %d DATA CHANNEL '%s' OPEN", chan->conn->pc, chan->dc, gettid(), chan->label);
internal_error(true, "WEBRTC[%d],DC[%d]: data channel opened.", chan->conn->pc, chan->dc);
chan->open = true;
}
static void myClosedCallback(int id, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_DC *chan = user_ptr;
internal_fatal(chan->dc != id, "WEBRTC[%d],DC[%d]: dc mismatch, expected %d, got %d", chan->conn->pc, chan->dc, chan->dc, id);
__atomic_store_n(&chan->open, false, __ATOMIC_RELAXED);
internal_error(true, "WEBRTC[%d],DC[%d]: data channel closed.", chan->conn->pc, chan->dc);
netdata_spinlock_lock(&chan->conn->channels.spinlock);
DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(chan->conn->channels.head, chan, link.prev, link.next);
netdata_spinlock_unlock(&chan->conn->channels.spinlock);
log_access("WEBRTC[%d],DC[%d]: %d DATA CHANNEL '%s' CLOSED", chan->conn->pc, chan->dc, gettid(), chan->label);
freez(chan->label);
freez(chan);
}
static void myErrorCallback(int id, const char *error, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_DC *chan = user_ptr;
internal_fatal(chan->dc != id, "WEBRTC[%d],DC[%d]: dc mismatch, expected %d, got %d", chan->conn->pc, chan->dc, chan->dc, id);
error("WEBRTC[%d],DC[%d]: ERROR: '%s'", chan->conn->pc, chan->dc, error);
}
static void myMessageCallback(int id, const char *message, int size, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_DC *chan = user_ptr;
internal_fatal(chan->dc != id, "WEBRTC[%d],DC[%d]: dc mismatch, expected %d, got %d", chan->conn->pc, chan->dc, chan->dc, id);
internal_fatal(!webrtc_dc_is_open(chan), "WEBRTC[%d],DC[%d]: received message on closed channel", chan->conn->pc, chan->dc);
bool binary = (size >= 0);
if(size < 0)
size = -size;
webrtc_execute_api_request(chan, message, size, binary);
}
//#define WEBRTC_MAX_REQUEST_SIZE 65536
//
//static void myAvailableCallback(int id, void *user_ptr) {
// webrtc_set_thread_name();
//
// WEBRTC_DC *chan = user_ptr;
// internal_fatal(chan->dc != id, "WEBRTC[%d],DC[%d]: dc mismatch, expected %d, got %d", chan->conn->pc, chan->dc, chan->dc, id);
//
// internal_fatal(!chan->open, "WEBRTC[%d],DC[%d]: received message on closed channel", chan->conn->pc, chan->dc);
//
// int size = WEBRTC_MAX_REQUEST_SIZE;
// char buffer[WEBRTC_MAX_REQUEST_SIZE];
// while(rtcReceiveMessage(id, buffer, &size) == RTC_ERR_SUCCESS) {
// bool binary = (size >= 0);
// if(size < 0)
// size = -size;
//
// webrtc_execute_api_request(chan, message, size, binary);
// }
//}
static void myDataChannelCallback(int pc, int dc, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_CONN *conn = user_ptr;
internal_fatal(conn->pc != pc, "WEBRTC[%d]: pc mismatch, expected %d, got %d", conn->pc, conn->pc, pc);
WEBRTC_DC *chan = callocz(1, sizeof(WEBRTC_DC));
chan->dc = dc;
chan->conn = conn;
netdata_spinlock_lock(&conn->channels.spinlock);
DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(conn->channels.head, chan, link.prev, link.next);
netdata_spinlock_unlock(&conn->channels.spinlock);
rtcSetUserPointer(dc, chan);
char label[1024 + 1];
rtcGetDataChannelLabel(dc, label, 1024);
label[1024] = '\0';
chan->label = strdupz(label);
if(rtcSetOpenCallback(dc, myOpenCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d],DC[%d]: rtcSetOpenCallback() failed.", conn->pc, chan->dc);
if(rtcSetClosedCallback(dc, myClosedCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d],DC[%d]: rtcSetClosedCallback() failed.", conn->pc, chan->dc);
if(rtcSetErrorCallback(dc, myErrorCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d],DC[%d]: rtcSetErrorCallback() failed.", conn->pc, chan->dc);
if(rtcSetMessageCallback(dc, myMessageCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d],DC[%d]: rtcSetMessageCallback() failed.", conn->pc, chan->dc);
// if(rtcSetAvailableCallback(dc, myAvailableCallback) != RTC_ERR_SUCCESS)
// error("WEBRTC[%d],DC[%d]: rtcSetAvailableCallback() failed.", conn->pc, chan->dc);
internal_error(true, "WEBRTC[%d],DC[%d]: new data channel with label '%s'", chan->conn->pc, chan->dc, chan->label);
}
// ----------------------------------------------------------------------------
// webrtc connection
static inline void webrtc_destroy_connection_unsafe(WEBRTC_CONN *conn) {
if(conn->state == RTC_CLOSED) {
netdata_spinlock_lock(&conn->channels.spinlock);
WEBRTC_DC *chan = conn->channels.head;
netdata_spinlock_unlock(&conn->channels.spinlock);
if(!chan) {
internal_error(true, "WEBRTC[%d]: destroying connection", conn->pc);
DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(webrtc_base.unsafe.head, conn, link.prev, link.next);
freez(conn);
}
else {
internal_error(true, "WEBRTC[%d]: not destroying closed connection because it has data channels running", conn->pc);
}
}
}
static void cleanupConnections() {
netdata_spinlock_lock(&webrtc_base.unsafe.spinlock);
WEBRTC_CONN *conn = webrtc_base.unsafe.head;
while(conn) {
WEBRTC_CONN *conn_next = conn->link.next;
webrtc_destroy_connection_unsafe(conn);
conn = conn_next;
}
netdata_spinlock_unlock(&webrtc_base.unsafe.spinlock);
}
static WEBRTC_CONN *webrtc_create_connection(void) {
WEBRTC_CONN *conn = callocz(1, sizeof(WEBRTC_CONN));
netdata_spinlock_init(&conn->response.spinlock);
netdata_spinlock_init(&conn->channels.spinlock);
netdata_spinlock_lock(&webrtc_base.unsafe.spinlock);
DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(webrtc_base.unsafe.head, conn, link.prev, link.next);
netdata_spinlock_unlock(&webrtc_base.unsafe.spinlock);
return conn;
}
static void myDescriptionCallback(int pc __maybe_unused, const char *sdp, const char *type, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_CONN *conn = user_ptr;
internal_fatal(conn->pc != pc, "WEBRTC[%d]: pc mismatch, expected %d, got %d", conn->pc, conn->pc, pc);
internal_error(true, "WEBRTC[%d]: local description type '%s': %s", conn->pc, type, sdp);
netdata_spinlock_lock(&conn->response.spinlock);
if(!conn->response.candidates) {
buffer_json_member_add_string(conn->response.wb, "sdp", sdp);
buffer_json_member_add_string(conn->response.wb, "type", type);
conn->response.sdp = true;
}
netdata_spinlock_unlock(&conn->response.spinlock);
conn->local_max_message_size = find_max_message_size_in_sdp(sdp);
}
static void myCandidateCallback(int pc __maybe_unused, const char *cand, const char *mid __maybe_unused, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_CONN *conn = user_ptr;
internal_fatal(conn->pc != pc, "WEBRTC[%d]: pc mismatch, expected %d, got %d", conn->pc, conn->pc, pc);
netdata_spinlock_lock(&conn->response.spinlock);
if(!conn->response.candidates) {
buffer_json_member_add_array(conn->response.wb, "candidates");
conn->response.candidates = true;
}
internal_error(true, "WEBRTC[%d]: local candidate '%s', mid '%s'", conn->pc, cand, mid);
buffer_json_add_array_item_string(conn->response.wb, cand);
netdata_spinlock_unlock(&conn->response.spinlock);
}
static void myStateChangeCallback(int pc __maybe_unused, rtcState state, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_CONN *conn = user_ptr;
internal_fatal(conn->pc != pc, "WEBRTC[%d]: pc mismatch, expected %d, got %d", conn->pc, conn->pc, pc);
conn->state = state;
switch(state) {
case RTC_NEW:
internal_error(true, "WEBRTC[%d]: new connection...", conn->pc);
break;
case RTC_CONNECTING:
log_access("WEBRTC[%d]: %d CONNECTING", conn->pc, gettid());
internal_error(true, "WEBRTC[%d]: connecting...", conn->pc);
break;
case RTC_CONNECTED:
log_access("WEBRTC[%d]: %d CONNECTED", conn->pc, gettid());
internal_error(true, "WEBRTC[%d]: connected!", conn->pc);
break;
case RTC_DISCONNECTED:
log_access("WEBRTC[%d]: %d DISCONNECTED", conn->pc, gettid());
internal_error(true, "WEBRTC[%d]: disconnected.", conn->pc);
break;
case RTC_FAILED:
log_access("WEBRTC[%d]: %d CONNECTION FAILED", conn->pc, gettid());
internal_error(true, "WEBRTC[%d]: failed.", conn->pc);
break;
case RTC_CLOSED:
log_access("WEBRTC[%d]: %d CONNECTION CLOSED", conn->pc, gettid());
internal_error(true, "WEBRTC[%d]: closed.", conn->pc);
netdata_spinlock_lock(&webrtc_base.unsafe.spinlock);
webrtc_destroy_connection_unsafe(conn);
netdata_spinlock_unlock(&webrtc_base.unsafe.spinlock);
break;
}
}
static void myGatheringStateCallback(int pc __maybe_unused, rtcGatheringState state, void *user_ptr) {
webrtc_set_thread_name();
WEBRTC_CONN *conn = user_ptr;
internal_fatal(conn->pc != pc, "WEBRTC[%d]: pc mismatch, expected %d, got %d", conn->pc, conn->pc, pc);
conn->gathering_state = state;
switch(state) {
case RTC_GATHERING_NEW:
internal_error(true, "WEBRTC[%d]: gathering...", conn->pc);
break;
case RTC_GATHERING_INPROGRESS:
internal_error(true, "WEBRTC[%d]: gathering in progress...", conn->pc);
break;
case RTC_GATHERING_COMPLETE:
internal_error(true, "WEBRTC[%d]: gathering complete!", conn->pc);
break;
}
}
int webrtc_new_connection(const char *sdp, BUFFER *wb) {
if(unlikely(!webrtc_base.enabled)) {
buffer_flush(wb);
buffer_strcat(wb, "WebRTC is not enabled on this agent.");
wb->content_type = CT_TEXT_PLAIN;
return HTTP_RESP_BAD_REQUEST;
}
cleanupConnections();
if(unlikely(!sdp || !*sdp)) {
buffer_flush(wb);
buffer_strcat(wb, "No SDP message posted with the request");
wb->content_type = CT_TEXT_PLAIN;
return HTTP_RESP_BAD_REQUEST;
}
buffer_flush(wb);
buffer_json_initialize(wb, "\"", "\"", 0, true, false);
wb->content_type = CT_APPLICATION_JSON;
WEBRTC_CONN *conn = webrtc_create_connection();
conn->response.wb = wb;
conn->max_message_size = WEBRTC_DEFAULT_REMOTE_MAX_MESSAGE_SIZE;
conn->local_max_message_size = WEBRTC_OUR_MAX_MESSAGE_SIZE;
conn->remote_max_message_size = find_max_message_size_in_sdp(sdp);
conn->config.iceServers = (const char **)webrtc_base.iceServers;
conn->config.iceServersCount = webrtc_base.iceServersCount;
conn->config.proxyServer = webrtc_base.proxyServer;
conn->config.bindAddress = webrtc_base.bindAddress;
conn->config.certificateType = RTC_CERTIFICATE_DEFAULT;
conn->config.iceTransportPolicy = RTC_TRANSPORT_POLICY_ALL;
conn->config.enableIceTcp = true; // libnice only
conn->config.enableIceUdpMux = true; // libjuice only
conn->config.disableAutoNegotiation = false;
conn->config.forceMediaTransport = false;
conn->config.portRangeBegin = 0; // 0 means automatic
conn->config.portRangeEnd = 0; // 0 means automatic
conn->config.mtu = 0; // <= 0 means automatic
conn->config.maxMessageSize = WEBRTC_OUR_MAX_MESSAGE_SIZE; // <= 0 means default
conn->pc = rtcCreatePeerConnection(&conn->config);
rtcSetUserPointer(conn->pc, conn);
if(rtcSetLocalDescriptionCallback(conn->pc, myDescriptionCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetLocalDescriptionCallback() failed", conn->pc);
if(rtcSetLocalCandidateCallback(conn->pc, myCandidateCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetLocalCandidateCallback() failed", conn->pc);
if(rtcSetStateChangeCallback(conn->pc, myStateChangeCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetStateChangeCallback() failed", conn->pc);
if(rtcSetGatheringStateChangeCallback(conn->pc, myGatheringStateCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetGatheringStateChangeCallback() failed", conn->pc);
if(rtcSetDataChannelCallback(conn->pc, myDataChannelCallback) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetDataChannelCallback() failed", conn->pc);
// initialize the handshake
internal_error(true, "WEBRTC[%d]: setting remote sdp: %s", conn->pc, sdp);
if(rtcSetRemoteDescription(conn->pc, sdp, "offer") != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetRemoteDescription() failed", conn->pc);
// initiate the handshake process
if(conn->config.disableAutoNegotiation) {
if(rtcSetLocalDescription(conn->pc, NULL) != RTC_ERR_SUCCESS)
error("WEBRTC[%d]: rtcSetLocalDescription() failed", conn->pc);
}
bool logged = false;
while(conn->gathering_state != RTC_GATHERING_COMPLETE) {
if(!logged) {
logged = true;
internal_error(true, "WEBRTC[%d]: Waiting for gathering to complete", conn->pc);
}
usleep(1000);
}
if(logged)
internal_error(true, "WEBRTC[%d]: Gathering finished, our answer is ready", conn->pc);
internal_fatal(!conn->response.sdp, "WEBRTC[%d]: response does not have an SDP: %s", conn->pc, buffer_tostring(conn->response.wb));
internal_fatal(!conn->response.candidates, "WEBRTC[%d]: response does not have candidates: %s", conn->pc, buffer_tostring(conn->response.wb));
conn->max_message_size = MIN(conn->local_max_message_size, conn->remote_max_message_size);
if(conn->max_message_size < WEBRTC_COMPRESSED_HEADER_SIZE)
conn->max_message_size = WEBRTC_COMPRESSED_HEADER_SIZE;
buffer_json_finalize(wb);
return HTTP_RESP_OK;
}
#else // ! HAVE_LIBDATACHANNEL
void webrtc_initialize() {
;
}
int webrtc_new_connection(const char *sdp __maybe_unused, BUFFER *wb) {
buffer_flush(wb);
buffer_strcat(wb, "WEBRTC is not available on this server");
wb->content_type = CT_TEXT_PLAIN;
return HTTP_RESP_BAD_REQUEST;
}
void webrtc_close_all_connections() {
;
}
#endif // ! HAVE_LIBDATACHANNEL