/* This callback handles Janus API requests */ static int janus_websockets_common_callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char ip[256]; #ifdef HAVE_LIBWEBSOCKETS_PEER_SIMPLE lws_get_peer_simple(wsi, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s\n", log_prefix, wsi, ip); #else char name[256]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); #endif if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ lws_callback_on_writable(wsi); return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Prepare the session */ ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; g_atomic_int_set(&ws_client->destroyed, 0); ws_client->ts = janus_transport_session_create(ws_client, NULL); /* Let us know when the WebSocket channel becomes writeable */ lws_callback_on_writable(wsi); JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client->ts, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(g_atomic_int_get(&ws_client->destroyed)) return 0; /* Is this a new message, or part of a fragmented one? */ const size_t remaining = lws_remaining_packet_payload(wsi); if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } if(remaining > 0 || !lws_is_final_fragment(wsi)) { /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client->ts, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->ts->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if (buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } janus_mutex_unlock(&ws_client->ts->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; }
int janus_mqtt_init(janus_transport_callbacks *callback, const char *config_path) { if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Initializing context */ janus_mqtt_context *ctx = g_malloc0(sizeof(struct janus_mqtt_context)); ctx->gateway = callback; context_ = ctx; /* Prepare the transport session (again, just one) */ mqtt_session = janus_transport_session_create(context_, NULL); /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_MQTT_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { janus_config_print(config); } /* Handle configuration */ janus_config_item *url_item = janus_config_get_item_drilldown(config, "general", "url"); const char *url = g_strdup((url_item && url_item->value) ? url_item->value : "tcp://localhost:1883"); janus_config_item *client_id_item = janus_config_get_item_drilldown(config, "general", "client_id"); const char *client_id = g_strdup((client_id_item && client_id_item->value) ? client_id_item->value : "guest"); janus_config_item *username_item = janus_config_get_item_drilldown(config, "general", "username"); ctx->connect.username = g_strdup((username_item && username_item->value) ? username_item->value : "guest"); janus_config_item *password_item = janus_config_get_item_drilldown(config, "general", "password"); ctx->connect.password = g_strdup((password_item && password_item->value) ? password_item->value : "guest"); janus_config_item *json_item = janus_config_get_item_drilldown(config, "general", "json"); if(json_item && json_item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(json_item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(json_item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format_ = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(json_item->value, "compact")) { /* Compact, so no spaces between separators */ json_format_ = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", json_item->value); json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_MQTT_NAME); } /* Check if we need to enable SSL support */ janus_config_item *ssl = janus_config_get_item_drilldown(config, "general", "ssl_enable"); if(ssl && ssl->value && janus_is_true(ssl->value)) { if(strstr(url, "ssl://") != url) JANUS_LOG(LOG_WARN, "SSL enabled, but MQTT url doesn't start with ssl://...\n"); ctx->ssl_enable = TRUE; janus_config_item *cacertfile = janus_config_get_item_drilldown(config, "general", "cacertfile"); if(!cacertfile || !cacertfile->value) { JANUS_LOG(LOG_FATAL, "Missing CA certificate for MQTT integration...\n"); goto error; } ctx->cacert_file = g_strdup(cacertfile->value); janus_config_item *certfile = janus_config_get_item_drilldown(config, "general", "certfile"); ctx->cert_file = (certfile && certfile->value) ? g_strdup(certfile->value) : NULL; janus_config_item *keyfile = janus_config_get_item_drilldown(config, "general", "keyfile"); ctx->key_file = (keyfile && keyfile->value) ? g_strdup(keyfile->value) : NULL; if(ctx->cert_file && !ctx->key_file) { JANUS_LOG(LOG_FATAL, "Certificate is set but key isn't for MQTT integration...\n"); goto error; } if(!ctx->cert_file && ctx->key_file) { JANUS_LOG(LOG_FATAL, "Key is set but certificate isn't for MQTT integration...\n"); goto error; } janus_config_item *verify = janus_config_get_item_drilldown(config, "general", "verify_peer"); ctx->verify_peer = (verify && verify->value && janus_is_true(verify->value)) ? TRUE : FALSE; } else { JANUS_LOG(LOG_INFO, "MQTT SSL support disabled\n"); if(strstr(url, "ssl://") == url) JANUS_LOG(LOG_WARN, "SSL disabled, but MQTT url starts with ssl:// instead of tcp://...\n"); } /* Connect configuration */ janus_config_item *keep_alive_interval_item = janus_config_get_item_drilldown(config, "general", "keep_alive_interval"); ctx->connect.keep_alive_interval = (keep_alive_interval_item && keep_alive_interval_item->value) ? atoi(keep_alive_interval_item->value) : 20; janus_config_item *cleansession_item = janus_config_get_item_drilldown(config, "general", "cleansession"); ctx->connect.cleansession = (cleansession_item && cleansession_item->value) ? atoi(cleansession_item->value) : 0; /* Disconnect configuration */ janus_config_item *disconnect_timeout_item = janus_config_get_item_drilldown(config, "general", "disconnect_timeout"); ctx->disconnect.timeout = (disconnect_timeout_item && disconnect_timeout_item->value) ? atoi(disconnect_timeout_item->value) : 100; janus_config_item *enable_item = janus_config_get_item_drilldown(config, "general", "enable"); if(enable_item && enable_item->value && janus_is_true(enable_item->value)) { janus_mqtt_api_enabled_ = TRUE; /* Subscribe configuration */ { janus_config_item *topic_item = janus_config_get_item_drilldown(config, "general", "subscribe_topic"); if(!topic_item || !topic_item->value) { JANUS_LOG(LOG_FATAL, "Missing topic for incoming messages for MQTT integration...\n"); goto error; } ctx->subscribe.topic = g_strdup(topic_item->value); janus_config_item *qos_item = janus_config_get_item_drilldown(config, "general", "subscribe_qos"); ctx->subscribe.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1; } /* Publish configuration */ { janus_config_item *topic_item = janus_config_get_item_drilldown(config, "general", "publish_topic"); if(!topic_item || !topic_item->value) { JANUS_LOG(LOG_FATAL, "Missing topic for outgoing messages for MQTT integration...\n"); goto error; } ctx->publish.topic = g_strdup(topic_item->value); janus_config_item *qos_item = janus_config_get_item_drilldown(config, "general", "publish_qos"); ctx->publish.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1; } } else { janus_mqtt_api_enabled_ = FALSE; ctx->subscribe.topic = NULL; ctx->publish.topic = NULL; } /* Admin configuration */ janus_config_item *admin_enable_item = janus_config_get_item_drilldown(config, "admin", "admin_enable"); if(admin_enable_item && admin_enable_item->value && janus_is_true(admin_enable_item->value)) { janus_mqtt_admin_api_enabled_ = TRUE; /* Admin subscribe configuration */ { janus_config_item *topic_item = janus_config_get_item_drilldown(config, "admin", "subscribe_topic"); if(!topic_item || !topic_item->value) { JANUS_LOG(LOG_FATAL, "Missing topic for incoming admin messages for MQTT integration...\n"); goto error; } ctx->admin.subscribe.topic = g_strdup(topic_item->value); janus_config_item *qos_item = janus_config_get_item_drilldown(config, "admin", "subscribe_qos"); ctx->admin.subscribe.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1; } /* Admin publish configuration */ { janus_config_item *topic_item = janus_config_get_item_drilldown(config, "admin", "publish_topic"); if(!topic_item || !topic_item->value) { JANUS_LOG(LOG_FATAL, "Missing topic for outgoing admin messages for MQTT integration...\n"); goto error; } ctx->admin.publish.topic = g_strdup(topic_item->value); janus_config_item *qos_item = janus_config_get_item_drilldown(config, "admin", "publish_qos"); ctx->admin.publish.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1; } } else { janus_mqtt_admin_api_enabled_ = FALSE; ctx->admin.subscribe.topic = NULL; ctx->admin.publish.topic = NULL; } if(!janus_mqtt_api_enabled_ && !janus_mqtt_admin_api_enabled_) { JANUS_LOG(LOG_WARN, "MQTT support disabled for both Janus and Admin API, giving up\n"); goto error; } /* Creating a client */ if(MQTTAsync_create( &ctx->client, url, client_id, MQTTCLIENT_PERSISTENCE_NONE, NULL) != MQTTASYNC_SUCCESS) { JANUS_LOG(LOG_FATAL, "Can't connect to MQTT broker: error creating client...\n"); goto error; } if(MQTTAsync_setCallbacks( ctx->client, ctx, janus_mqtt_client_connection_lost, janus_mqtt_client_message_arrived, janus_mqtt_client_delivery_complete) != MQTTASYNC_SUCCESS) { JANUS_LOG(LOG_FATAL, "Can't connect to MQTT broker: error setting up callbacks...\n"); goto error; } /* Connecting to the broker */ int rc = janus_mqtt_client_connect(ctx); if(rc != MQTTASYNC_SUCCESS) { JANUS_LOG(LOG_FATAL, "Can't connect to MQTT broker, return code: %d\n", rc); goto error; } return 0; error: /* If we got here, something went wrong */ janus_transport_session_destroy(mqtt_session); janus_mqtt_client_destroy_context(&ctx); g_free((char *)url); g_free((char *)client_id); g_free(config); return -1; }
/* Transport implementation */ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the Janus core */ gateway = callback; /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_RABBITMQ_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config == NULL) { JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_RABBITMQ_PACKAGE); g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_RABBITMQ_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); config = janus_config_parse(filename); } if(config != NULL) janus_config_print(config); janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general"); janus_config_category *config_admin = janus_config_get_create(config, NULL, janus_config_type_category, "admin"); janus_config_item *item = janus_config_get(config, config_general, janus_config_type_item, "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_RABBITMQ_NAME); } /* Handle configuration, starting from the server details */ item = janus_config_get(config, config_general, janus_config_type_item, "host"); if(item && item->value) rmqhost = g_strdup(item->value); else rmqhost = g_strdup("localhost"); int rmqport = AMQP_PROTOCOL_PORT; item = janus_config_get(config, config_general, janus_config_type_item, "port"); if(item && item->value) rmqport = atoi(item->value); /* Credentials and Virtual Host */ item = janus_config_get(config, config_general, janus_config_type_item, "vhost"); if(item && item->value) vhost = g_strdup(item->value); else vhost = g_strdup("/"); item = janus_config_get(config, config_general, janus_config_type_item, "username"); if(item && item->value) username = g_strdup(item->value); else username = g_strdup("guest"); item = janus_config_get(config, config_general, janus_config_type_item, "password"); if(item && item->value) password = g_strdup(item->value); else password = g_strdup("guest"); /* SSL config*/ gboolean ssl_enabled = FALSE; gboolean ssl_verify_peer = FALSE; gboolean ssl_verify_hostname = FALSE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "ssl_enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'ssl_enable' property, please update it to 'ssl_enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_INFO, "RabbitMQ SSL support disabled\n"); } else { ssl_enabled = TRUE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_cacert"); if(item && item->value) ssl_cacert_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_cert"); if(item && item->value) ssl_cert_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_key"); if(item && item->value) ssl_key_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_verify_peer"); if(item && item->value && janus_is_true(item->value)) ssl_verify_peer = TRUE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_verify_hostname"); if(item && item->value && janus_is_true(item->value)) ssl_verify_hostname = TRUE; } /* Now check if the Janus API must be supported */ item = janus_config_get(config, config_general, janus_config_type_item, "enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'enable' property, please update it to 'enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Janus API)\n"); } else { /* Parse configuration */ item = janus_config_get(config, config_general, janus_config_type_item, "to_janus"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); goto error; } to_janus = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "from_janus"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); goto error; } from_janus = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "janus_exchange"); if(!item || !item->value) { JANUS_LOG(LOG_INFO, "Missing name of outgoing exchange for RabbitMQ integration, using default\n"); } else { janus_exchange = g_strdup(item->value); } if (janus_exchange == NULL) { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s)\n", rmqhost, rmqport, to_janus, from_janus); } else { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s) exch: (%s)\n", rmqhost, rmqport, to_janus, from_janus, janus_exchange); } rmq_janus_api_enabled = TRUE; } /* Do the same for the admin API */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "admin_enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'admin_enable' property, please update it to 'admin_enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Admin API)\n"); } else { /* Parse configuration */ item = janus_config_get(config, config_admin, janus_config_type_item, "to_janus_admin"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); goto error; } to_janus_admin = g_strdup(item->value); item = janus_config_get(config, config_admin, janus_config_type_item, "from_janus_admin"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); goto error; } from_janus_admin = g_strdup(item->value); JANUS_LOG(LOG_INFO, "RabbitMQ support for Admin API enabled, %s:%d (%s/%s)\n", rmqhost, rmqport, to_janus_admin, from_janus_admin); rmq_admin_api_enabled = TRUE; } if(!rmq_janus_api_enabled && !rmq_admin_api_enabled) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled for both Janus and Admin API, giving up\n"); goto error; } else { /* FIXME We currently support a single application, create a new janus_rabbitmq_client instance */ rmq_client = g_malloc0(sizeof(janus_rabbitmq_client)); /* Connect */ rmq_client->rmq_conn = amqp_new_connection(); amqp_socket_t *socket = NULL; int status; JANUS_LOG(LOG_VERB, "Creating RabbitMQ socket...\n"); if (ssl_enabled) { socket = amqp_ssl_socket_new(rmq_client->rmq_conn); if(socket == NULL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error creating socket...\n"); goto error; } if(ssl_verify_peer) { amqp_ssl_socket_set_verify_peer(socket, 1); } else { amqp_ssl_socket_set_verify_peer(socket, 0); } if(ssl_verify_hostname) { amqp_ssl_socket_set_verify_hostname(socket, 1); } else { amqp_ssl_socket_set_verify_hostname(socket, 0); } if(ssl_cacert_file) { status = amqp_ssl_socket_set_cacert(socket, ssl_cacert_file); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error setting CA certificate... (%s)\n", amqp_error_string2(status)); goto error; } } if(ssl_cert_file && ssl_key_file) { status = amqp_ssl_socket_set_key(socket, ssl_cert_file, ssl_key_file); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error setting key... (%s)\n", amqp_error_string2(status)); goto error; } } } else { socket = amqp_tcp_socket_new(rmq_client->rmq_conn); if(socket == NULL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error creating socket...\n"); goto error; } } JANUS_LOG(LOG_VERB, "Connecting to RabbitMQ server...\n"); status = amqp_socket_open(socket, rmqhost, rmqport); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error opening socket... (%s)\n", amqp_error_string2(status)); goto error; } JANUS_LOG(LOG_VERB, "Logging in...\n"); amqp_rpc_reply_t result = amqp_login(rmq_client->rmq_conn, vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username, password); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error logging in... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } rmq_client->rmq_channel = 1; JANUS_LOG(LOG_VERB, "Opening channel...\n"); amqp_channel_open(rmq_client->rmq_conn, rmq_client->rmq_channel); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error opening channel... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } rmq_client->janus_exchange = amqp_empty_bytes; if(janus_exchange != NULL) { JANUS_LOG(LOG_VERB, "Declaring exchange...\n"); rmq_client->janus_exchange = amqp_cstring_bytes(janus_exchange); amqp_exchange_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->janus_exchange, amqp_cstring_bytes(JANUS_RABBITMQ_EXCHANGE_TYPE), 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error diclaring exchange... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->janus_api_enabled = FALSE; if(rmq_janus_api_enabled) { rmq_client->janus_api_enabled = TRUE; JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus); rmq_client->to_janus_queue = amqp_cstring_bytes(to_janus); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus); rmq_client->from_janus_queue = amqp_cstring_bytes(from_janus); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error consuming... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->admin_api_enabled = FALSE; if(rmq_admin_api_enabled) { rmq_client->admin_api_enabled = TRUE; JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus_admin); rmq_client->to_janus_admin_queue = amqp_cstring_bytes(to_janus_admin); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus_admin); rmq_client->from_janus_admin_queue = amqp_cstring_bytes(from_janus_admin); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error consuming... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->messages = g_async_queue_new(); rmq_client->destroy = 0; /* Prepare the transport session (again, just one) */ rmq_session = janus_transport_session_create(rmq_client, NULL); /* Start the threads */ GError *error = NULL; rmq_client->in_thread = g_thread_try_new("rmq_in_thread", &janus_rmq_in_thread, rmq_client, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_FATAL, "Got error %d (%s) trying to launch the RabbitMQ incoming thread...\n", error->code, error->message ? error->message : "??"); janus_transport_session_destroy(rmq_session); g_free(rmq_client); janus_config_destroy(config); return -1; } rmq_client->out_thread = g_thread_try_new("rmq_out_thread", &janus_rmq_out_thread, rmq_client, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_FATAL, "Got error %d (%s) trying to launch the RabbitMQ outgoing thread...\n", error->code, error->message ? error->message : "??"); janus_transport_session_destroy(rmq_session); g_free(rmq_client); janus_config_destroy(config); return -1; } janus_mutex_init(&rmq_client->mutex); /* Done */ JANUS_LOG(LOG_INFO, "Setup of RabbitMQ integration completed\n"); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); gateway->notify_event(&janus_rabbitmq_transport, rmq_session, info); } } janus_config_destroy(config); config = NULL; /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_RABBITMQ_NAME); return 0; error: /* If we got here, something went wrong */ g_free(rmq_client); g_free(rmqhost); g_free(vhost); g_free(username); g_free(password); g_free(janus_exchange); g_free(to_janus); g_free(from_janus); g_free(to_janus_admin); g_free(from_janus_admin); g_free(ssl_cacert_file); g_free(ssl_cert_file); g_free(ssl_key_file); if(config) janus_config_destroy(config); return -1; }
/* Thread */ void *janus_pfunix_thread(void *data) { JANUS_LOG(LOG_INFO, "Unix Sockets thread started\n"); int fds = 0; struct pollfd poll_fds[1024]; /* FIXME Should we allow for more clients? */ char buffer[BUFFER_SIZE]; struct iovec iov[1]; struct msghdr msg; memset(&msg, 0, sizeof(msg)); memset(iov, 0, sizeof(iov)); iov[0].iov_base = buffer; iov[0].iov_len = sizeof(buffer); msg.msg_iov = iov; msg.msg_iovlen = 1; while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* Prepare poll list of file descriptors */ fds = 0; /* Writeable monitor */ poll_fds[fds].fd = write_fd[0]; poll_fds[fds].events = POLLIN; fds++; if(pfd > -1) { /* Janus API */ poll_fds[fds].fd = pfd; poll_fds[fds].events = POLLIN; fds++; } if(admin_pfd > -1) { /* Admin API */ poll_fds[fds].fd = admin_pfd; poll_fds[fds].events = POLLIN; fds++; } /* Iterate on available clients, to see if we need to POLLIN or POLLOUT too */ janus_mutex_lock(&clients_mutex); GHashTableIter iter; gpointer value; g_hash_table_iter_init(&iter, clients_by_fd); while(g_hash_table_iter_next(&iter, NULL, &value)) { janus_pfunix_client *client = value; if(client->fd > -1) { poll_fds[fds].fd = client->fd; poll_fds[fds].events = g_async_queue_length(client->messages) > 0 ? POLLIN | POLLOUT : POLLIN; fds++; } } janus_mutex_unlock(&clients_mutex); /* Start polling */ int res = poll(poll_fds, fds, -1); if(res == 0) continue; if(res < 0) { if(errno == EINTR) { JANUS_LOG(LOG_HUGE, "Got an EINTR (%s) polling the Unix Sockets descriptors, ignoring...\n", strerror(errno)); continue; } JANUS_LOG(LOG_ERR, "poll() failed: %d (%s)\n", errno, strerror(errno)); break; } int i = 0; for(i=0; i<fds; i++) { if(poll_fds[i].revents & (POLLERR | POLLHUP)) { /* Socket error? Shall we do something? */ if(poll_fds[i].fd == write_fd[0]) { /* Error in the wake-up socketpair, that sucks: try recreating it */ JANUS_LOG(LOG_WARN, "Error polling wake-up socketpair: %s...\n", poll_fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP"); close(write_fd[0]); write_fd[0] = -1; close(write_fd[1]); write_fd[1] = -1; if(socketpair(PF_LOCAL, SOCK_STREAM, 0, write_fd) < 0) { JANUS_LOG(LOG_FATAL, "Error creating socket pair for writeable events: %d, %s\n", errno, strerror(errno)); continue; } } else if(poll_fds[i].fd == pfd) { /* Error in the Janus API socket */ JANUS_LOG(LOG_WARN, "Error polling Unix Sockets Janus API interface (%s), disabling it\n", poll_fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP"); close(pfd); pfd = -1; continue; } else if(poll_fds[i].fd == admin_pfd) { /* Error in the Admin API socket */ JANUS_LOG(LOG_WARN, "Error polling Unix Sockets Admin API interface (%s), disabling it\n", poll_fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP"); close(admin_pfd); admin_pfd = -1; continue; } else { /* Error in a client socket, find and remove it */ janus_mutex_lock(&clients_mutex); janus_pfunix_client *client = g_hash_table_lookup(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); if(client == NULL) { /* We're not handling this, ignore */ janus_mutex_unlock(&clients_mutex); continue; } JANUS_LOG(LOG_INFO, "Unix Sockets client disconnected (%d)\n", poll_fds[i].fd); /* Notify core */ gateway->transport_gone(&janus_pfunix_transport, client->ts); /* Notify handlers about this transport being gone */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("disconnected")); gateway->notify_event(&janus_pfunix_transport, client->ts, info); } /* Close socket */ shutdown(SHUT_RDWR, poll_fds[i].fd); close(poll_fds[i].fd); client->fd = -1; /* Destroy the client */ g_hash_table_remove(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); g_hash_table_remove(clients, client); /* Unref the transport instance */ janus_transport_session_destroy(client->ts); janus_mutex_unlock(&clients_mutex); continue; } continue; } if(poll_fds[i].revents & POLLOUT) { /* Find the client from its file descriptor */ janus_mutex_lock(&clients_mutex); janus_pfunix_client *client = g_hash_table_lookup(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); if(client != NULL) { char *payload = NULL; while((payload = g_async_queue_try_pop(client->messages)) != NULL) { int res = 0; do { if(client->fd < 0) break; res = write(client->fd, payload, strlen(payload)); } while(res == -1 && errno == EINTR); /* FIXME Should we check if sent everything? */ JANUS_LOG(LOG_HUGE, "Written %d/%zu bytes on %d\n", res, strlen(payload), client->fd); g_free(payload); } if(client->session_timeout) { /* We should actually get rid of this connection, now */ shutdown(SHUT_RDWR, poll_fds[i].fd); close(poll_fds[i].fd); client->fd = -1; /* Destroy the client */ g_hash_table_remove(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); g_hash_table_remove(clients, client); if(client->messages != NULL) { char *response = NULL; while((response = g_async_queue_try_pop(client->messages)) != NULL) { g_free(response); } g_async_queue_unref(client->messages); } g_free(client); } } janus_mutex_unlock(&clients_mutex); } if(poll_fds[i].revents & POLLIN) { if(poll_fds[i].fd == write_fd[0]) { /* Read and ignore: we use this to unlock the poll if there's data to write */ (void)read(poll_fds[i].fd, buffer, BUFFER_SIZE); } else if(poll_fds[i].fd == pfd || poll_fds[i].fd == admin_pfd) { /* Janus/Admin API: accept the new client (SOCK_SEQPACKET) or receive data (SOCK_DGRAM) */ struct sockaddr_un address; socklen_t addrlen = sizeof(address); if((poll_fds[i].fd == pfd && !dgram) || (poll_fds[i].fd == admin_pfd && !admin_dgram)) { /* SOCK_SEQPACKET */ int cfd = accept(poll_fds[i].fd, (struct sockaddr *) &address, &addrlen); if(cfd > -1) { JANUS_LOG(LOG_INFO, "Got new Unix Sockets %s API client: %d\n", poll_fds[i].fd == pfd ? "Janus" : "Admin", cfd); /* Allocate new client */ janus_pfunix_client *client = g_malloc(sizeof(janus_pfunix_client)); client->fd = cfd; memset(&client->addr, 0, sizeof(client->addr)); client->admin = (poll_fds[i].fd == admin_pfd); /* API client type */ client->messages = g_async_queue_new(); client->session_timeout = FALSE; /* Create a transport instance as well */ client->ts = janus_transport_session_create(client, janus_pfunix_client_free); /* Take note of this new client */ janus_mutex_lock(&clients_mutex); g_hash_table_insert(clients_by_fd, GINT_TO_POINTER(cfd), client); g_hash_table_insert(clients, client, client); janus_mutex_unlock(&clients_mutex); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", client->admin ? json_true() : json_false()); json_object_set_new(info, "fd", json_integer(client->fd)); gateway->notify_event(&janus_pfunix_transport, client->ts, info); } } } else { /* SOCK_DGRAM */ struct sockaddr_storage address; res = recvfrom(poll_fds[i].fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&address, &addrlen); if(res < 0) { if(errno != EAGAIN && errno != EWOULDBLOCK) { JANUS_LOG(LOG_ERR, "Error reading from client (%s API)...\n", poll_fds[i].fd == pfd ? "Janus" : "Admin"); } continue; } buffer[res] = '\0'; /* Is this a new client, or one we knew about already? */ struct sockaddr_un *uaddr = (struct sockaddr_un *)&address; if(strlen(uaddr->sun_path) == 0) { /* No path provided, drop the packet */ JANUS_LOG(LOG_WARN, "Dropping packet from unknown source (no path provided)\n"); continue; } janus_mutex_lock(&clients_mutex); janus_pfunix_client *client = g_hash_table_lookup(clients_by_path, uaddr->sun_path); if(client == NULL) { JANUS_LOG(LOG_INFO, "Got new Unix Sockets %s API client: %s\n", poll_fds[i].fd == pfd ? "Janus" : "Admin", uaddr->sun_path); /* Allocate new client */ client = g_malloc(sizeof(janus_pfunix_client)); client->fd = -1; memcpy(&client->addr, uaddr, sizeof(struct sockaddr_un)); client->admin = (poll_fds[i].fd == admin_pfd); /* API client type */ client->messages = g_async_queue_new(); client->session_timeout = FALSE; /* Create a transport instance as well */ client->ts = janus_transport_session_create(client, janus_pfunix_client_free); /* Take note of this new client */ g_hash_table_insert(clients_by_path, uaddr->sun_path, client); g_hash_table_insert(clients, client, client); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", client->admin ? json_true() : json_false()); json_object_set_new(info, "fd", json_integer(client->fd)); json_object_set_new(info, "type", json_string("SOCK_DGRAM")); gateway->notify_event(&janus_pfunix_transport, client->ts, info); } } janus_mutex_unlock(&clients_mutex); JANUS_LOG(LOG_VERB, "Message from client %s (%d bytes)\n", uaddr->sun_path, res); JANUS_LOG(LOG_HUGE, "%s\n", buffer); /* Parse the JSON payload */ json_error_t error; json_t *root = json_loads(buffer, 0, &error); /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_pfunix_transport, client->ts, NULL, client->admin, root, &error); } } else { /* Client data: receive message */ iov[0].iov_len = sizeof(buffer); res = recvmsg(poll_fds[i].fd, &msg, MSG_WAITALL); if(res < 0) { if(errno != EAGAIN && errno != EWOULDBLOCK) { JANUS_LOG(LOG_ERR, "Error reading from client %d...\n", poll_fds[i].fd); } continue; } if(msg.msg_flags & MSG_TRUNC) { /* Apparently our buffer is not large enough? */ JANUS_LOG(LOG_WARN, "Incoming message from client %d truncated (%d bytes), dropping it...\n", poll_fds[i].fd, res); continue; } /* Find the client from its file descriptor */ janus_mutex_lock(&clients_mutex); janus_pfunix_client *client = g_hash_table_lookup(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); if(client == NULL) { janus_mutex_unlock(&clients_mutex); JANUS_LOG(LOG_WARN, "Got data from unknown Unix Sockets client %d, closing connection...\n", poll_fds[i].fd); /* Close socket */ shutdown(SHUT_RDWR, poll_fds[i].fd); close(poll_fds[i].fd); continue; } if(res == 0) { JANUS_LOG(LOG_INFO, "Unix Sockets client disconnected (%d)\n", poll_fds[i].fd); /* Notify core */ gateway->transport_gone(&janus_pfunix_transport, client->ts); /* Notify handlers about this transport being gone */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("disconnected")); gateway->notify_event(&janus_pfunix_transport, client->ts, info); } /* Close socket */ shutdown(SHUT_RDWR, poll_fds[i].fd); close(poll_fds[i].fd); client->fd = -1; /* Destroy the client */ g_hash_table_remove(clients_by_fd, GINT_TO_POINTER(poll_fds[i].fd)); g_hash_table_remove(clients, client); /* Unref the transport instance */ janus_transport_session_destroy(client->ts); janus_mutex_unlock(&clients_mutex); continue; } janus_mutex_unlock(&clients_mutex); /* If we got here, there's data to handle */ buffer[res] = '\0'; JANUS_LOG(LOG_VERB, "Message from client %d (%d bytes)\n", poll_fds[i].fd, res); JANUS_LOG(LOG_HUGE, "%s\n", buffer); /* Parse the JSON payload */ json_error_t error; json_t *root = json_loads(buffer, 0, &error); /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_pfunix_transport, client->ts, NULL, client->admin, root, &error); } } } } socklen_t addrlen = sizeof(struct sockaddr_un); void *addr = g_malloc(addrlen+1); if(pfd > -1) { /* Unlink the path name first */ #ifdef HAVE_LIBSYSTEMD if((getsockname(pfd, (struct sockaddr *)addr, &addrlen) != -1) && (FALSE == sd_socket)) { #else if(getsockname(pfd, (struct sockaddr *)addr, &addrlen) != -1) { #endif JANUS_LOG(LOG_INFO, "Unlinking %s\n", ((struct sockaddr_un *)addr)->sun_path); unlink(((struct sockaddr_un *)addr)->sun_path); } /* Close the socket */ close(pfd); } pfd = -1; if(admin_pfd > -1) { /* Unlink the path name first */ #ifdef HAVE_LIBSYSTEMD if((getsockname(admin_pfd, (struct sockaddr *)addr, &addrlen) != -1) && (FALSE == admin_sd_socket)) { #else if(getsockname(admin_pfd, (struct sockaddr *)addr, &addrlen) != -1) { #endif JANUS_LOG(LOG_INFO, "Unlinking %s\n", ((struct sockaddr_un *)addr)->sun_path); unlink(((struct sockaddr_un *)addr)->sun_path); } /* Close the socket */ close(admin_pfd); } admin_pfd = -1; g_free(addr); g_hash_table_destroy(clients_by_path); g_hash_table_destroy(clients_by_fd); g_hash_table_destroy(clients); /* Done */ JANUS_LOG(LOG_INFO, "Unix Sockets thread ended\n"); return NULL; }
/* Transport implementation */ int janus_nanomsg_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_NANOMSG_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { /* Handle configuration */ janus_config_print(config); janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_NANOMSG_NAME); } /* First of all, initialize the pipeline for writeable notifications */ write_nfd[0] = nn_socket(AF_SP, NN_PULL); write_nfd[1] = nn_socket(AF_SP, NN_PUSH); if(nn_bind(write_nfd[0], "inproc://janus") < 0) { JANUS_LOG(LOG_WARN, "Error configuring internal Nanomsg pipeline... %d (%s)\n", errno, nn_strerror(errno)); return -1; /* No point in keeping the plugin loaded */ } if(nn_connect(write_nfd[1], "inproc://janus") < 0) { JANUS_LOG(LOG_WARN, "Error configuring internal Nanomsg pipeline...%d (%s)\n", errno, nn_strerror(errno)); return -1; /* No point in keeping the plugin loaded */ } /* Setup the Janus API Nanomsg server(s) */ item = janus_config_get_item_drilldown(config, "general", "enabled"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Nanomsg server disabled (Janus API)\n"); } else { item = janus_config_get_item_drilldown(config, "general", "address"); const char *address = item && item->value ? item->value : NULL; item = janus_config_get_item_drilldown(config, "general", "mode"); const char *mode = item && item->value ? item->value : NULL; if(mode == NULL) mode = "bind"; nfd = nn_socket(AF_SP, NN_PAIR); if(nfd < 0) { JANUS_LOG(LOG_ERR, "Error creating Janus API Nanomsg socket: %d (%s)\n", errno, nn_strerror(errno)); } else { if(!strcasecmp(mode, "bind")) { /* Bind to this address */ nfd_addr = nn_bind(nfd, address); if(nfd_addr < 0) { JANUS_LOG(LOG_ERR, "Error binding Janus API Nanomsg socket to address '%s': %d (%s)\n", address, errno, nn_strerror(errno)); nn_close(nfd); nfd = -1; } } else if(!strcasecmp(mode, "connect")) { /* Connect to this address */ nfd_addr = nn_connect(nfd, address); if(nfd_addr < 0) { JANUS_LOG(LOG_ERR, "Error connecting Janus API Nanomsg socket to address '%s': %d (%s)\n", address, errno, nn_strerror(errno)); nn_close(nfd); nfd = -1; } } else { /* Unsupported mode */ JANUS_LOG(LOG_ERR, "Unsupported mode '%s'\n", mode); nn_close(nfd); nfd = -1; } } } /* Do the same for the Admin API, if enabled */ item = janus_config_get_item_drilldown(config, "admin", "admin_enabled"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Nanomsg server disabled (Admin API)\n"); } else { item = janus_config_get_item_drilldown(config, "admin", "admin_address"); const char *address = item && item->value ? item->value : NULL; item = janus_config_get_item_drilldown(config, "general", "admin_mode"); const char *mode = item && item->value ? item->value : NULL; if(mode == NULL) mode = "bind"; admin_nfd = nn_socket(AF_SP, NN_PAIR); if(admin_nfd < 0) { JANUS_LOG(LOG_ERR, "Error creating Admin API Nanomsg socket: %d (%s)\n", errno, nn_strerror(errno)); } else { if(!strcasecmp(mode, "bind")) { /* Bind to this address */ admin_nfd_addr = nn_bind(admin_nfd, address); if(admin_nfd_addr < 0) { JANUS_LOG(LOG_ERR, "Error binding Admin API Nanomsg socket to address '%s': %d (%s)\n", address, errno, nn_strerror(errno)); nn_close(admin_nfd); admin_nfd = -1; } } else if(!strcasecmp(mode, "connect")) { /* Connect to this address */ admin_nfd_addr = nn_connect(admin_nfd, address); if(admin_nfd_addr < 0) { JANUS_LOG(LOG_ERR, "Error connecting Admin API Nanomsg socket to address '%s': %d (%s)\n", address, errno, nn_strerror(errno)); nn_close(admin_nfd); admin_nfd = -1; } } else { /* Unsupported mode */ JANUS_LOG(LOG_ERR, "Unsupported mode '%s'\n", mode); nn_close(admin_nfd); admin_nfd = -1; } } } } janus_config_destroy(config); config = NULL; if(nfd < 0 && admin_nfd < 0) { JANUS_LOG(LOG_WARN, "No Nanomsg server started, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } /* Create the clients */ memset(&client, 0, sizeof(janus_nanomsg_client)); if(nfd > -1) { client.admin = FALSE; client.messages = g_async_queue_new(); /* Create a transport instance as well */ client.ts = janus_transport_session_create(&client, NULL); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("created")); json_object_set_new(info, "admin_api", json_false()); json_object_set_new(info, "socket", json_integer(nfd)); gateway->notify_event(&janus_nanomsg_transport, client.ts, info); } } memset(&admin_client, 0, sizeof(janus_nanomsg_client)); if(admin_nfd > -1) { admin_client.admin = TRUE; admin_client.messages = g_async_queue_new(); /* Create a transport instance as well */ admin_client.ts = janus_transport_session_create(&admin_client, NULL); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("created")); json_object_set_new(info, "admin_api", json_true()); json_object_set_new(info, "socket", json_integer(admin_nfd)); gateway->notify_event(&janus_nanomsg_transport, admin_client.ts, info); } } /* Start the Nanomsg service thread */ GError *error = NULL; nanomsg_thread = g_thread_try_new("nanomsg thread", &janus_nanomsg_thread, NULL, &error); if(!nanomsg_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Nanomsg thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_NANOMSG_NAME); return 0; }