void janus_rabbitmq_destroy(void) { if(!g_atomic_int_get(&initialized)) return; g_atomic_int_set(&stopping, 1); if(rmq_client) { rmq_client->destroy = 1; g_async_queue_push(rmq_client->messages, &exit_message); if(rmq_client->in_thread) g_thread_join(rmq_client->in_thread); if(rmq_client->out_thread) g_thread_join(rmq_client->out_thread); if(rmq_client->rmq_conn && rmq_client->rmq_channel) { amqp_channel_close(rmq_client->rmq_conn, rmq_client->rmq_channel, AMQP_REPLY_SUCCESS); amqp_connection_close(rmq_client->rmq_conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(rmq_client->rmq_conn); } } g_free(rmq_client); janus_transport_session_destroy(rmq_session); 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); g_atomic_int_set(&initialized, 0); g_atomic_int_set(&stopping, 0); JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_RABBITMQ_NAME); }
static void janus_websockets_destroy_client( janus_websockets_client *ws_client, struct lws *wsi, const char *log_prefix) { if(!ws_client || !g_atomic_int_compare_and_exchange(&ws_client->destroyed, 0, 1)) return; /* Cleanup */ janus_mutex_lock(&ws_client->ts->mutex); JANUS_LOG(LOG_INFO, "[%s-%p] Destroying WebSocket client\n", log_prefix, wsi); ws_client->wsi = NULL; /* 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_websockets_transport, ws_client->ts, info); } /* Notify core */ gateway->transport_gone(&janus_websockets_transport, ws_client->ts); ws_client->ts->transport_p = NULL; /* Remove messages queue too, if needed */ if(ws_client->messages != NULL) { char *response = NULL; while((response = g_async_queue_try_pop(ws_client->messages)) != NULL) { g_free(response); } g_async_queue_unref(ws_client->messages); } /* ... and the shared buffers */ g_free(ws_client->incoming); ws_client->incoming = NULL; g_free(ws_client->buffer); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; janus_mutex_unlock(&ws_client->ts->mutex); janus_transport_session_destroy(ws_client->ts); }
void janus_mqtt_destroy(void) { JANUS_LOG(LOG_INFO, "Disconnecting MQTT client...\n"); janus_transport_session_destroy(mqtt_session); janus_mqtt_client_disconnect(context_); }
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; }
/* 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_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_nanomsg_thread(void *data) { JANUS_LOG(LOG_INFO, "Nanomsg thread started\n"); int fds = 0; struct nn_pollfd poll_nfds[3]; /* FIXME Should we allow for more clients? */ char buffer[BUFFER_SIZE]; while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* Prepare poll list of file descriptors */ fds = 0; /* Writeable monitor */ poll_nfds[fds].fd = write_nfd[0]; poll_nfds[fds].events = NN_POLLIN; fds++; if(nfd > -1) { /* Janus API */ poll_nfds[fds].fd = nfd; poll_nfds[fds].events = NN_POLLIN; if(client.messages != NULL && g_async_queue_length(client.messages) > 0) poll_nfds[fds].events |= NN_POLLOUT; fds++; } if(admin_nfd > -1) { /* Admin API */ poll_nfds[fds].fd = admin_nfd; poll_nfds[fds].events = NN_POLLIN; if(admin_client.messages != NULL && g_async_queue_length(admin_client.messages) > 0) poll_nfds[fds].events |= NN_POLLOUT; fds++; } /* Start polling */ int res = nn_poll(poll_nfds, fds, -1); if(res == 0) continue; if(res < 0) { if(errno == EINTR) { JANUS_LOG(LOG_HUGE, "Got an EINTR (%s) polling the Nanomsg descriptors, ignoring...\n", nn_strerror(errno)); continue; } JANUS_LOG(LOG_ERR, "poll() failed: %d (%s)\n", errno, nn_strerror(errno)); break; } int i = 0; for(i=0; i<fds; i++) { /* FIXME Is there a Nanomsg equivalent of POLLERR? */ if(poll_nfds[i].revents & NN_POLLOUT) { /* Find the client from its file descriptor */ if(poll_nfds[i].fd == nfd || poll_nfds[i].fd == admin_nfd) { char *payload = NULL; while((payload = g_async_queue_try_pop(poll_nfds[i].fd == nfd ? client.messages : admin_client.messages)) != NULL) { int res = nn_send(poll_nfds[i].fd, payload, strlen(payload), 0); /* FIXME Should we check if sent everything? */ JANUS_LOG(LOG_HUGE, "Written %d/%zu bytes on %d\n", res, strlen(payload), poll_nfds[i].fd); g_free(payload); } } } if(poll_nfds[i].revents & NN_POLLIN) { if(poll_nfds[i].fd == write_nfd[0]) { /* Read and ignore: we use this to unlock the poll if there's data to write */ (void)nn_recv(poll_nfds[i].fd, buffer, BUFFER_SIZE, 0); } else if(poll_nfds[i].fd == nfd || poll_nfds[i].fd == admin_nfd) { /* Janus/Admin API: get the message from the client */ int res = nn_recv(poll_nfds[i].fd, buffer, BUFFER_SIZE, 0); if(res < 0) { JANUS_LOG(LOG_WARN, "Error receiving %s API message... %d (%s)\n", poll_nfds[i].fd == nfd ? "Janus" : "Admin", errno, nn_strerror(errno)); continue; } /* If we got here, there's data to handle */ buffer[res] = '\0'; JANUS_LOG(LOG_VERB, "Got %s API message (%d bytes)\n", poll_nfds[i].fd == nfd ? "Janus" : "Admin", 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_nanomsg_transport, poll_nfds[i].fd == nfd ? client.ts : admin_client.ts, NULL, poll_nfds[i].fd == nfd ? FALSE : TRUE, root, &error); } } } } nn_close(write_nfd[0]); nn_close(write_nfd[1]); if(nfd > -1) { nn_shutdown(nfd, nfd_addr); nn_close(nfd); janus_transport_session_destroy(client.ts); client.ts = NULL; } if(admin_nfd > -1) { nn_shutdown(admin_nfd, admin_nfd_addr); nn_close(admin_nfd); janus_transport_session_destroy(admin_client.ts); admin_client.ts = NULL; } /* Done */ JANUS_LOG(LOG_INFO, "Nanomsg thread ended\n"); return NULL; }