/* DTLS alert callback */ void janus_dtls_callback(const SSL *ssl, int where, int ret) { /* We only care about alerts */ if (!(where & SSL_CB_ALERT)) { return; } janus_dtls_srtp *dtls = SSL_get_ex_data(ssl, 0); if(!dtls) { JANUS_LOG(LOG_ERR, "No DTLS session related to this alert...\n"); return; } janus_ice_component *component = dtls->component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No ICE component related to this alert...\n"); return; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No ICE stream related to this alert...\n"); return; } janus_ice_handle *handle = stream->handle; if(!handle) { JANUS_LOG(LOG_ERR, "No ICE handle related to this alert...\n"); return; } if(stream->stream_id == handle->data_id) { /* FIXME BADLY We got a DTLS alert on the Data channel, we ignore it for now */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] DTLS alert triggered on stream %"SCNu16", but it's the data channel so we don't care...\n", handle->handle_id, stream->stream_id); return; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS alert triggered on stream %"SCNu16" (component %"SCNu16"), closing...\n", handle->handle_id, stream->stream_id, component->component_id); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT); if(handle->iceloop) g_main_loop_quit(handle->iceloop); janus_plugin *plugin = (janus_plugin *)handle->app; if(plugin != NULL) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Telling the plugin about it (%s)\n", handle->handle_id, plugin->get_name()); if(plugin && plugin->hangup_media) plugin->hangup_media(handle->app_handle); janus_ice_notify_hangup(handle, "DTLS alert"); } } }
/* DTLS alert callback */ void janus_dtls_callback(const SSL *ssl, int where, int ret) { /* We only care about alerts */ if (!(where & SSL_CB_ALERT)) { return; } janus_dtls_srtp *dtls = SSL_get_ex_data(ssl, 0); if(!dtls) { JANUS_LOG(LOG_ERR, "No DTLS session related to this alert...\n"); return; } dtls->srtp_valid = 0; janus_ice_component *component = dtls->component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No ICE component related to this alert...\n"); return; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No ICE stream related to this alert...\n"); return; } janus_ice_handle *handle = stream->handle; if(!stream) { JANUS_LOG(LOG_ERR, "No ICE handle related to this alert...\n"); return; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS alert received, closing...\n", handle->handle_id); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT); if(handle->iceloop) g_main_loop_quit(handle->iceloop); janus_plugin *plugin = (janus_plugin *)handle->app; if(plugin != NULL) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Telling the plugin about it (%s)\n", handle->handle_id, plugin->get_name()); if(plugin && plugin->hangup_media) plugin->hangup_media(handle->app_handle); } } }
/* Plugin implementation */ int janus_sampleevh_init(const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ gboolean enabled = FALSE; char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_SAMPLEEVH_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); /* Setup the sample event handler, if required */ janus_config_item *item = janus_config_get_item_drilldown(config, "general", "enabled"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Sample event handler disabled (Janus API)\n"); } else { /* Backend to send events to */ item = janus_config_get_item_drilldown(config, "general", "backend"); if(!item || !item->value || strstr(item->value, "http") != item->value) { JANUS_LOG(LOG_WARN, "Missing or invalid backend\n"); } else { backend = g_strdup(item->value); /* Any credentials needed? */ item = janus_config_get_item_drilldown(config, "general", "backend_user"); backend_user = (item && item->value) ? g_strdup(item->value) : NULL; item = janus_config_get_item_drilldown(config, "general", "backend_pwd"); backend_pwd = (item && item->value) ? g_strdup(item->value) : NULL; /* Any specific setting for retransmissions? */ item = janus_config_get_item_drilldown(config, "general", "max_retransmissions"); if(item && item->value) { int mr = atoi(item->value); if(mr < 0) { JANUS_LOG(LOG_WARN, "Invalid negative value for 'max_retransmissions', using default (%d)\n", max_retransmissions); } else if(mr == 0) { JANUS_LOG(LOG_WARN, "Retransmissions disabled (max_retransmissions=0)\n"); max_retransmissions = 0; } else { max_retransmissions = mr; } } item = janus_config_get_item_drilldown(config, "general", "retransmissions_backoff"); if(item && item->value) { int rb = atoi(item->value); if(rb <= 0) { JANUS_LOG(LOG_WARN, "Invalid negative or null value for 'retransmissions_backoff', using default (%d)\n", retransmissions_backoff); } else { retransmissions_backoff = rb; } } /* Which events should we subscribe to? */ item = janus_config_get_item_drilldown(config, "general", "events"); if(item && item->value) { if(!strcasecmp(item->value, "none")) { /* Don't subscribe to anything at all */ janus_flags_reset(&janus_sampleevh.events_mask); } else if(!strcasecmp(item->value, "all")) { /* Subscribe to everything */ janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_ALL); } else { /* Check what we need to subscribe to */ gchar **subscribe = g_strsplit(item->value, ",", -1); if(subscribe != NULL) { gchar *index = subscribe[0]; if(index != NULL) { int i=0; while(index != NULL) { while(isspace(*index)) index++; if(strlen(index)) { if(!strcasecmp(index, "sessions")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_SESSION); } else if(!strcasecmp(index, "handles")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_HANDLE); } else if(!strcasecmp(index, "jsep")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_JSEP); } else if(!strcasecmp(index, "webrtc")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_WEBRTC); } else if(!strcasecmp(index, "media")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_MEDIA); } else if(!strcasecmp(index, "plugins")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_PLUGIN); } else if(!strcasecmp(index, "transports")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_TRANSPORT); } else if(!strcasecmp(index, "core")) { janus_flags_set(&janus_sampleevh.events_mask, JANUS_EVENT_TYPE_CORE); } else { JANUS_LOG(LOG_WARN, "Unknown event type '%s'\n", index); } } i++; index = subscribe[i]; } } g_strfreev(subscribe); } } } /* Is grouping of events ok? */ item = janus_config_get_item_drilldown(config, "general", "grouping"); group_events = item && item->value && janus_is_true(item->value); /* Done */ enabled = TRUE; } } } janus_config_destroy(config); config = NULL; if(!enabled) { JANUS_LOG(LOG_FATAL, "Sample event handler not enabled/needed, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } JANUS_LOG(LOG_VERB, "Sample event handler configured: %s\n", backend); /* Initialize libcurl, needed for forwarding events via HTTP POST */ curl_global_init(CURL_GLOBAL_ALL); /* Initialize the events queue */ events = g_async_queue_new_full((GDestroyNotify) janus_sampleevh_event_free); g_atomic_int_set(&initialized, 1); /* Launch the thread that will handle incoming events */ GError *error = NULL; handler_thread = g_thread_try_new("janus sampleevh handler", janus_sampleevh_handler, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SampleEventHandler handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_SAMPLEEVH_NAME); return 0; }
void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len) { if(dtls == NULL) { JANUS_LOG(LOG_ERR, "No DTLS-SRTP stack, no incoming message...\n"); return; } janus_ice_component *component = (janus_ice_component *)dtls->component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No component, no DTLS...\n"); return; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No stream, no DTLS...\n"); return; } janus_ice_handle *handle = stream->handle; if(!handle || !handle->agent) { JANUS_LOG(LOG_ERR, "No handle/agent, no DTLS...\n"); return; } if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Alert already triggered, clearing up...\n", handle->handle_id); return; } if(!dtls->ssl || !dtls->read_bio) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No DTLS stuff for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); return; } janus_dtls_fd_bridge(dtls); int written = BIO_write(dtls->read_bio, buf, len); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Written %d of those bytes on the read BIO...\n", handle->handle_id, written); janus_dtls_fd_bridge(dtls); /* Try to read data */ char data[1500]; /* FIXME */ memset(&data, 0, 1500); int read = SSL_read(dtls->ssl, &data, 1500); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] ... and read %d of them from SSL...\n", handle->handle_id, read); if(read < 0) { unsigned long err = SSL_get_error(dtls->ssl, read); if(err == SSL_ERROR_SSL) { /* Ops, something went wrong with the DTLS handshake */ char error[200]; ERR_error_string_n(ERR_get_error(), error, 200); JANUS_LOG(LOG_ERR, "[%"SCNu64"] Handshake error: %s\n", handle->handle_id, error); return; } } janus_dtls_fd_bridge(dtls); if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_STOP) || janus_is_stopping()) { /* DTLS alert triggered, we should end it here */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Forced to stop it here...\n", handle->handle_id); return; } if(!SSL_is_init_finished(dtls->ssl)) { /* Nothing else to do for now */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Initialization not finished yet...\n", handle->handle_id); return; } if(dtls->ready) { /* There's data to be read? */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Any data available?\n", handle->handle_id); #ifdef HAVE_SCTP if(dtls->sctp != NULL && read > 0) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Sending data (%d bytes) to the SCTP stack...\n", handle->handle_id, read); janus_sctp_data_from_dtls(dtls->sctp, data, read); } #else if(read > 0) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Data available but Data Channels support disabled...\n", handle->handle_id); } #endif } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS established, yay!\n", handle->handle_id); /* Check the remote fingerprint */ X509 *rcert = SSL_get_peer_certificate(dtls->ssl); if(!rcert) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No remote certificate??\n", handle->handle_id); } else { unsigned int rsize; unsigned char rfingerprint[EVP_MAX_MD_SIZE]; char remote_fingerprint[160]; char *rfp = (char *)&remote_fingerprint; if(handle->remote_hashing && !strcasecmp(handle->remote_hashing, "sha-1")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Computing sha-1 fingerprint of remote certificate...\n", handle->handle_id); X509_digest(rcert, EVP_sha1(), (unsigned char *)rfingerprint, &rsize); } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Computing sha-256 fingerprint of remote certificate...\n", handle->handle_id); X509_digest(rcert, EVP_sha256(), (unsigned char *)rfingerprint, &rsize); } X509_free(rcert); rcert = NULL; unsigned int i = 0; for(i = 0; i < rsize; i++) { g_snprintf(rfp, 4, "%.2X:", rfingerprint[i]); rfp += 3; } *(rfp-1) = 0; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Remote fingerprint (%s) of the client is %s\n", handle->handle_id, handle->remote_hashing ? handle->remote_hashing : "sha-256", remote_fingerprint); if(!strcasecmp(remote_fingerprint, handle->remote_fingerprint ? handle->remote_fingerprint : "(none)")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint is a match!\n", handle->handle_id); dtls->dtls_state = JANUS_DTLS_STATE_CONNECTED; dtls->dtls_connected = janus_get_monotonic_time(); } else { /* FIXME NOT a match! MITM? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Fingerprint is NOT a match! got %s, expected %s\n", handle->handle_id, remote_fingerprint, handle->remote_fingerprint); dtls->dtls_state = JANUS_DTLS_STATE_FAILED; goto done; } if(dtls->dtls_state == JANUS_DTLS_STATE_CONNECTED) { if(component->stream_id == handle->audio_id || component->stream_id == handle->video_id) { /* Complete with SRTP setup */ unsigned char material[SRTP_MASTER_LENGTH*2]; unsigned char *local_key, *local_salt, *remote_key, *remote_salt; /* Export keying material for SRTP */ if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LENGTH*2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { /* Oops... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, couldn't extract SRTP keying material for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); goto done; } /* Key derivation (http://tools.ietf.org/html/rfc5764#section-4.2) */ if(dtls->dtls_role == JANUS_DTLS_ROLE_CLIENT) { local_key = material; remote_key = local_key + SRTP_MASTER_KEY_LENGTH; local_salt = remote_key + SRTP_MASTER_KEY_LENGTH; remote_salt = local_salt + SRTP_MASTER_SALT_LENGTH; } else { remote_key = material; local_key = remote_key + SRTP_MASTER_KEY_LENGTH; remote_salt = local_key + SRTP_MASTER_KEY_LENGTH; local_salt = remote_salt + SRTP_MASTER_SALT_LENGTH; } /* Build master keys and set SRTP policies */ /* Remote (inbound) */ crypto_policy_set_rtp_default(&(dtls->remote_policy.rtp)); crypto_policy_set_rtcp_default(&(dtls->remote_policy.rtcp)); dtls->remote_policy.ssrc.type = ssrc_any_inbound; unsigned char remote_policy_key[SRTP_MASTER_LENGTH]; dtls->remote_policy.key = (unsigned char *)&remote_policy_key; memcpy(dtls->remote_policy.key, remote_key, SRTP_MASTER_KEY_LENGTH); memcpy(dtls->remote_policy.key + SRTP_MASTER_KEY_LENGTH, remote_salt, SRTP_MASTER_SALT_LENGTH); #if HAS_DTLS_WINDOW_SIZE dtls->remote_policy.window_size = 128; dtls->remote_policy.allow_repeat_tx = 0; #endif dtls->remote_policy.next = NULL; /* Local (outbound) */ crypto_policy_set_rtp_default(&(dtls->local_policy.rtp)); crypto_policy_set_rtcp_default(&(dtls->local_policy.rtcp)); dtls->local_policy.ssrc.type = ssrc_any_outbound; unsigned char local_policy_key[SRTP_MASTER_LENGTH]; dtls->local_policy.key = (unsigned char *)&local_policy_key; memcpy(dtls->local_policy.key, local_key, SRTP_MASTER_KEY_LENGTH); memcpy(dtls->local_policy.key + SRTP_MASTER_KEY_LENGTH, local_salt, SRTP_MASTER_SALT_LENGTH); #if HAS_DTLS_WINDOW_SIZE dtls->local_policy.window_size = 128; dtls->local_policy.allow_repeat_tx = 0; #endif dtls->local_policy.next = NULL; /* Create SRTP sessions */ err_status_t res = srtp_create(&(dtls->srtp_in), &(dtls->remote_policy)); if(res != err_status_ok) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating inbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_get_srtp_error(res)); goto done; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created inbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); res = srtp_create(&(dtls->srtp_out), &(dtls->local_policy)); if(res != err_status_ok) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating outbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_get_srtp_error(res)); goto done; } dtls->srtp_valid = 1; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created outbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); } #ifdef HAVE_SCTP if(component->stream_id == handle->data_id || (janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE) && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS))) { /* FIXME Create SCTP association as well (5000 should be dynamic, from the SDP...) */ dtls->sctp = janus_sctp_association_create(dtls, handle->handle_id, 5000); if(dtls->sctp != NULL) { /* FIXME We need to start it in a thread, though, since it has blocking accept/connect stuff */ GError *error = NULL; g_thread_try_new("DTLS-SCTP", janus_dtls_sctp_setup_thread, dtls, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Got error %d (%s) trying to launch the DTLS-SCTP thread...\n", handle->handle_id, error->code, error->message ? error->message : "??"); } dtls->srtp_valid = 1; } } #endif dtls->ready = 1; } done: if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && dtls->srtp_valid) { /* Handshake successfully completed */ janus_ice_dtls_handshake_done(handle, component); } else { /* Something went wrong in either DTLS or SRTP... tell the plugin about it */ janus_dtls_callback(dtls->ssl, SSL_CB_ALERT, 0); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING); } } } }
char *janus_sdp_merge(janus_ice_handle *handle, const char *origsdp) { if(handle == NULL || origsdp == NULL) return NULL; sdp_session_t *anon = NULL; sdp_parser_t *parser = sdp_parse(home, origsdp, strlen(origsdp), 0); if(!(anon = sdp_session(parser))) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error parsing/merging SDP: %s\n", handle->handle_id, sdp_parsing_error(parser)); sdp_parser_free(parser); return NULL; } /* Prepare SDP to merge */ gchar buffer[512]; memset(buffer, 0, 512); char *sdp = (char*)calloc(BUFSIZE, sizeof(char)); if(sdp == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); sdp_parser_free(parser); return NULL; } sdp[0] = '\0'; /* FIXME Any Plan B to take into account? */ int planb = strstr(origsdp, "a=planb:") ? 1 : 0; if(planb) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B); } else { janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B); } /* Version v= */ g_strlcat(sdp, "v=0\r\n", BUFSIZE); /* Origin o= */ if(anon->sdp_origin) { g_snprintf(buffer, 512, "o=%s %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n", /* FIXME Should we fix the address? */ anon->sdp_origin->o_username ? anon->sdp_origin->o_username : "******", anon->sdp_origin->o_id, anon->sdp_origin->o_version); g_strlcat(sdp, buffer, BUFSIZE); } else { gint64 sessid = janus_get_monotonic_time(); gint64 version = sessid; /* FIXME This needs to be increased when it changes, so time should be ok */ g_snprintf(buffer, 512, "o=%s %"SCNi64" %"SCNi64" IN IP4 127.0.0.1\r\n", /* FIXME Should we fix the address? */ "-", sessid, version); g_strlcat(sdp, buffer, BUFSIZE); } /* Session name s= */ if(anon->sdp_subject && strlen(anon->sdp_subject) > 0) { g_snprintf(buffer, 512, "s=%s\r\n", anon->sdp_subject); } else { g_snprintf(buffer, 512, "s=%s\r\n", "Meetecho Janus"); } g_strlcat(sdp, buffer, BUFSIZE); /* Timing t= */ g_snprintf(buffer, 512, "t=%lu %lu\r\n", anon->sdp_time ? anon->sdp_time->t_start : 0, anon->sdp_time ? anon->sdp_time->t_stop : 0); g_strlcat(sdp, buffer, BUFSIZE); /* ICE Full or Lite? */ if(janus_ice_is_ice_lite_enabled()) { /* Janus is acting in ICE Lite mode, advertize this */ g_strlcat(sdp, "a=ice-lite\r\n", BUFSIZE); } /* bundle: add new global attribute */ int audio = (strstr(origsdp, "m=audio") != NULL); int video = (strstr(origsdp, "m=video") != NULL); #ifdef HAVE_SCTP int data = (strstr(origsdp, "DTLS/SCTP") && !strstr(origsdp, " 0 DTLS/SCTP")); #else int data = 0; #endif g_strlcat(sdp, "a=group:BUNDLE", BUFSIZE); if(audio) { g_snprintf(buffer, 512, " %s", handle->audio_mid ? handle->audio_mid : "audio"); g_strlcat(sdp, buffer, BUFSIZE); } if(video) { g_snprintf(buffer, 512, " %s", handle->video_mid ? handle->video_mid : "video"); g_strlcat(sdp, buffer, BUFSIZE); } if(data) { g_snprintf(buffer, 512, " %s", handle->data_mid ? handle->data_mid : "data"); g_strlcat(sdp, buffer, BUFSIZE); } g_strlcat(sdp, "\r\n", BUFSIZE); /* msid-semantic: add new global attribute */ g_strlcat(sdp, "a=msid-semantic: WMS janus\r\n", BUFSIZE); char wms[BUFSIZE]; memset(wms, 0, BUFSIZE); g_strlcat(wms, "WMS", BUFSIZE); /* Copy other global attributes, if any */ if(anon->sdp_attributes) { sdp_attribute_t *a = anon->sdp_attributes; while(a) { if(a->a_value == NULL) { g_snprintf(buffer, 512, "a=%s\r\n", a->a_name); g_strlcat(sdp, buffer, BUFSIZE); } else { g_snprintf(buffer, 512, "a=%s:%s\r\n", a->a_name, a->a_value); g_strlcat(sdp, buffer, BUFSIZE); } a = a->a_next; } } gboolean ipv6 = strstr(janus_get_public_ip(), ":") != NULL; /* Media lines now */ if(anon->sdp_media) { int audio = 0, video = 0; #ifdef HAVE_SCTP int data = 0; #endif sdp_media_t *m = anon->sdp_media; janus_ice_stream *stream = NULL; while(m) { if(m->m_type == sdp_media_audio && m->m_port > 0) { audio++; if(audio > 1 || !handle->audio_id) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (we have %d audio lines, and the id is %d)\n", handle->handle_id, audio, handle->audio_id); g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } /* Audio */ stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->audio_id)); if(stream == NULL) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (invalid stream %d)\n", handle->handle_id, handle->audio_id); g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } g_strlcat(sdp, "m=audio 1 RTP/SAVPF", BUFSIZE); } else if(m->m_type == sdp_media_video && m->m_port > 0) { video++; gint id = handle->video_id; if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) id = handle->audio_id > 0 ? handle->audio_id : handle->video_id; if(video > 1 || !id) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (we have %d video lines, and the id is %d)\n", handle->handle_id, video, janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE) ? handle->audio_id : handle->video_id); g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } /* Video */ stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id)); if(stream == NULL) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (invalid stream %d)\n", handle->handle_id, id); g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } g_strlcat(sdp, "m=video 1 RTP/SAVPF", BUFSIZE); #ifdef HAVE_SCTP } else if(m->m_type == sdp_media_application) { /* Is this SCTP for DataChannels? */ if(m->m_port > 0 && m->m_proto_name != NULL && !strcasecmp(m->m_proto_name, "DTLS/SCTP") && m->m_port > 0) { /* Yep */ data++; gint id = handle->data_id; if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) id = handle->audio_id > 0 ? handle->audio_id : handle->video_id; if(data > 1 || !id) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (we have %d SCTP lines, and the id is %d)\n", handle->handle_id, data, id); g_snprintf(buffer, 512, "m=%s 0 %s 0\r\n", m->m_type_name, m->m_proto_name); g_strlcat(sdp, buffer, BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } /* SCTP */ stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id)); if(stream == NULL) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (invalid stream %d)\n", handle->handle_id, id); g_snprintf(buffer, 512, "m=%s 0 %s 0\r\n", m->m_type_name, m->m_proto_name); g_strlcat(sdp, buffer, BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } g_strlcat(sdp, "m=application 1 DTLS/SCTP", BUFSIZE); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping unsupported application media line...\n", handle->handle_id); g_snprintf(buffer, 512, "m=%s 0 %s 0\r\n", m->m_type_name, m->m_proto_name); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } #endif } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id); g_snprintf(buffer, 512, "m=%s 0 %s 0\r\n", m->m_type_name, m->m_proto_name); g_strlcat(sdp, buffer, BUFSIZE); /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); m = m->m_next; continue; } /* Add formats now */ if(!m->m_rtpmaps) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] No RTP maps?? trying formats...\n", handle->handle_id); if(!m->m_format) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No formats either?? this sucks!\n", handle->handle_id); g_strlcat(sdp, " 0", BUFSIZE); /* FIXME Won't work apparently */ } else { sdp_list_t *fmt = m->m_format; while(fmt) { g_snprintf(buffer, 512, " %s", fmt->l_text); g_strlcat(sdp, buffer, BUFSIZE); fmt = fmt->l_next; } } } else { sdp_rtpmap_t *r = m->m_rtpmaps; while(r) { g_snprintf(buffer, 512, " %d", r->rm_pt); g_strlcat(sdp, buffer, BUFSIZE); r = r->rm_next; } } g_strlcat(sdp, "\r\n", BUFSIZE); /* Media connection c= */ g_snprintf(buffer, 512, "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip()); g_strlcat(sdp, buffer, BUFSIZE); /* Any bandwidth? */ if(m->m_bandwidths) { g_snprintf(buffer, 512, "b=%s:%lu\r\n", /* FIXME Are we doing this correctly? */ m->m_bandwidths->b_modifier_name ? m->m_bandwidths->b_modifier_name : "AS", m->m_bandwidths->b_value); g_strlcat(sdp, buffer, BUFSIZE); } /* a=mid:(audio|video|data) */ switch(m->m_type) { case sdp_media_audio: g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->audio_mid ? handle->audio_mid : "audio"); break; case sdp_media_video: g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->video_mid ? handle->video_mid : "video"); break; #ifdef HAVE_SCTP case sdp_media_application: /* FIXME sctpmap and webrtc-datachannel should be dynamic */ g_snprintf(buffer, 512, "a=mid:%s\r\na=sctpmap:5000 webrtc-datachannel 16\r\n", handle->data_mid ? handle->data_mid : "data"); break; #endif default: break; } g_strlcat(sdp, buffer, BUFSIZE); if(m->m_type != sdp_media_application) { /* What is the direction? */ switch(m->m_mode) { case sdp_sendonly: g_strlcat(sdp, "a=sendonly\r\n", BUFSIZE); break; case sdp_recvonly: g_strlcat(sdp, "a=recvonly\r\n", BUFSIZE); break; case sdp_inactive: g_strlcat(sdp, "a=inactive\r\n", BUFSIZE); break; case sdp_sendrecv: default: g_strlcat(sdp, "a=sendrecv\r\n", BUFSIZE); break; } /* rtcp-mux */ g_snprintf(buffer, 512, "a=rtcp-mux\n"); g_strlcat(sdp, buffer, BUFSIZE); /* RTP maps */ if(m->m_rtpmaps) { sdp_rtpmap_t *rm = NULL; for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) { g_snprintf(buffer, 512, "a=rtpmap:%u %s/%lu%s%s\r\n", rm->rm_pt, rm->rm_encoding, rm->rm_rate, rm->rm_params ? "/" : "", rm->rm_params ? rm->rm_params : ""); g_strlcat(sdp, buffer, BUFSIZE); } for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) { if(rm->rm_fmtp) { g_snprintf(buffer, 512, "a=fmtp:%u %s\r\n", rm->rm_pt, rm->rm_fmtp); g_strlcat(sdp, buffer, BUFSIZE); } } } } /* ICE ufrag and pwd, DTLS fingerprint setup and connection a= */ gchar *ufrag = NULL; gchar *password = NULL; nice_agent_get_local_credentials(handle->agent, stream->stream_id, &ufrag, &password); memset(buffer, 0, 100); g_snprintf(buffer, 512, "a=ice-ufrag:%s\r\n" "a=ice-pwd:%s\r\n" "a=ice-options:trickle\r\n" "a=fingerprint:sha-256 %s\r\n" "a=setup:%s\r\n" "a=connection:new\r\n", ufrag, password, janus_dtls_get_local_fingerprint(), janus_get_dtls_srtp_role(stream->dtls_role)); if(ufrag != NULL) g_free(ufrag); ufrag = NULL; if(password != NULL) g_free(password); password = NULL; g_strlcat(sdp, buffer, BUFSIZE); /* Copy existing media attributes, if any */ if(m->m_attributes) { sdp_attribute_t *a = m->m_attributes; while(a) { if(!strcmp(a->a_name, "planb")) { /* Skip the fake planb attribute, it's for internal use only */ a = a->a_next; continue; } if(a->a_value == NULL) { g_snprintf(buffer, 512, "a=%s\r\n", a->a_name); g_strlcat(sdp, buffer, BUFSIZE); } else { g_snprintf(buffer, 512, "a=%s:%s\r\n", a->a_name, a->a_value); g_strlcat(sdp, buffer, BUFSIZE); } a = a->a_next; } } /* Add last attributes, rtcp and ssrc (msid) */ if(!planb) { /* Single SSRC */ if(m->m_type == sdp_media_audio && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) { g_snprintf(buffer, 512, "a=ssrc:%"SCNu32" cname:janusaudio\r\n" "a=ssrc:%"SCNu32" msid:janus janusa0\r\n" "a=ssrc:%"SCNu32" mslabel:janus\r\n" "a=ssrc:%"SCNu32" label:janusa0\r\n", stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc); g_strlcat(sdp, buffer, BUFSIZE); } else if(m->m_type == sdp_media_video && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) { g_snprintf(buffer, 512, "a=ssrc:%"SCNu32" cname:janusvideo\r\n" "a=ssrc:%"SCNu32" msid:janus janusv0\r\n" "a=ssrc:%"SCNu32" mslabel:janus\r\n" "a=ssrc:%"SCNu32" label:janusv0\r\n", stream->video_ssrc, stream->video_ssrc, stream->video_ssrc, stream->video_ssrc); g_strlcat(sdp, buffer, BUFSIZE); } } else { /* Multiple SSRCs */ char mslabel[255]; memset(mslabel, 0, 255); if(m->m_attributes) { char id[256]; uint32_t ssrc = 0; sdp_attribute_t *a = m->m_attributes; while(a) { if(a->a_name == NULL || a->a_value == NULL || strcmp(a->a_name, "planb")) { a = a->a_next; continue; } if(sscanf(a->a_value, "%255s %"SCNu32"", id, &ssrc) != 2) { JANUS_LOG(LOG_ERR, "Error parsing 'planb' attribute, skipping it...\n"); a = a->a_next; continue; } JANUS_LOG(LOG_VERB, "Parsing 'planb' attribute: %s\n", a->a_value); /* Add proper SSRC attributes */ if(m->m_type == sdp_media_audio) { g_snprintf(buffer, 512, "a=ssrc:%"SCNu32" cname:%saudio\r\n" "a=ssrc:%"SCNu32" msid:%s %sa0\r\n" "a=ssrc:%"SCNu32" mslabel:%s\r\n" "a=ssrc:%"SCNu32" label:%sa0\r\n", ssrc, id, ssrc, id, id, ssrc, id, ssrc, id); } else if(m->m_type == sdp_media_video) { g_snprintf(buffer, 512, "a=ssrc:%"SCNu32" cname:%svideo\r\n" "a=ssrc:%"SCNu32" msid:%s %sv0\r\n" "a=ssrc:%"SCNu32" mslabel:%s\r\n" "a=ssrc:%"SCNu32" label:%sv0\r\n", ssrc, id, ssrc, id, id, ssrc, id, ssrc, id); } g_strlcat(sdp, buffer, BUFSIZE); /* Add to msid-semantic, if needed */ if(!strstr(wms, id)) { g_snprintf(mslabel, 255, " %s", id); g_strlcat(wms, mslabel, BUFSIZE); } /* Go on */ a = a->a_next; } } } /* And now the candidates */ janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 1); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX) && m->m_type != sdp_media_application) janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 2); /* Next */ m = m->m_next; } } /* Do we need to update the msid-semantic attribute? */ if(planb) { sdp = janus_string_replace(sdp, "WMS janus", wms); } sdp_parser_free(parser); JANUS_LOG(LOG_VERB, " -------------------------------------------\n"); JANUS_LOG(LOG_VERB, " >> Merged (%zu --> %zu bytes)\n", strlen(origsdp), strlen(sdp)); JANUS_LOG(LOG_VERB, " -------------------------------------------\n"); JANUS_LOG(LOG_VERB, "%s\n", sdp); return sdp; }
int janus_sdp_parse_candidate(janus_ice_stream *stream, const char *candidate, int trickle) { if(stream == NULL || candidate == NULL) return -1; janus_ice_handle *handle = stream->handle; if(handle == NULL) return -2; janus_mutex_lock(&handle->mutex); janus_ice_component *component = NULL; if(strstr(candidate, "candidate:") == candidate) { /* Skipping the 'candidate:' prefix Firefox puts in trickle candidates */ candidate += strlen("candidate:"); } char rfoundation[32], rtransport[4], rip[40], rtype[6], rrelip[40]; guint32 rcomponent, rpriority, rport, rrelport; int res = sscanf(candidate, "%31s %30u %3s %30u %39s %30u typ %5s %*s %39s %*s %30u", rfoundation, &rcomponent, rtransport, &rpriority, rip, &rport, rtype, rrelip, &rrelport); if(res < 7) { /* Failed to parse this address, can it be IPv6? */ if(!janus_ice_is_ipv6_enabled()) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Received IPv6 candidate, but IPv6 support is disabled...\n", handle->handle_id); janus_mutex_unlock(&handle->mutex); return res; } } if(res >= 7) { /* Add remote candidate */ component = g_hash_table_lookup(stream->components, GUINT_TO_POINTER(rcomponent)); if(component == NULL) { if(rcomponent == 2 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, stream->stream_id); } else { JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- No such component %d in stream %d?\n", handle->handle_id, rcomponent, stream->stream_id); } } else { if(rcomponent == 2 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, stream->stream_id); janus_mutex_unlock(&handle->mutex); return 0; } //~ if(trickle) { //~ if(component->dtls != NULL) { //~ /* This component is already ready, ignore this further candidate */ //~ JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Ignoring this candidate, the component is already ready\n", handle->handle_id); //~ janus_mutex_unlock(&handle->mutex); //~ return 0; //~ } //~ } component->component_id = rcomponent; component->stream_id = stream->stream_id; NiceCandidate *c = NULL; if(!strcasecmp(rtype, "host")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:host %s:%d\n", handle->handle_id, rcomponent, stream->stream_id, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_HOST); #ifdef HAVE_LIBNICE_TCP } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_HOST); #endif } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport); } } else if(!strcasecmp(rtype, "srflx")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:srflx %s:%d --> %s:%d \n", handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE); #ifdef HAVE_LIBNICE_TCP } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE); #endif } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport); } } else if(!strcasecmp(rtype, "prflx")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:prflx %s:%d --> %s:%d\n", handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); #ifdef HAVE_LIBNICE_TCP } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); #endif } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport); } } else if(!strcasecmp(rtype, "relay")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:relay %s:%d --> %s:%d\n", handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); /* We only support UDP/TCP/TLS... */ if(strcasecmp(rtransport, "udp") && strcasecmp(rtransport, "tcp") && strcasecmp(rtransport, "tls")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport); } else { c = nice_candidate_new(NICE_CANDIDATE_TYPE_RELAYED); } } else { /* FIXME What now? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Unknown remote candidate type:%s for component:%d stream:%d!\n", handle->handle_id, rtype, rcomponent, stream->stream_id); } if(c != NULL) { c->component_id = rcomponent; c->stream_id = stream->stream_id; #ifndef HAVE_LIBNICE_TCP c->transport = NICE_CANDIDATE_TRANSPORT_UDP; #else if(!strcasecmp(rtransport, "udp")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Transport: UDP\n", handle->handle_id); c->transport = NICE_CANDIDATE_TRANSPORT_UDP; } else { /* Check the type (https://tools.ietf.org/html/rfc6544#section-4.5) */ const char *type = NULL; int ctype = 0; if(strstr(candidate, "tcptype active")) { type = "active"; ctype = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE; } else if(strstr(candidate, "tcptype passive")) { type = "passive"; ctype = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE; } else if(strstr(candidate, "tcptype so")) { type = "so"; ctype = NICE_CANDIDATE_TRANSPORT_TCP_SO; } else { /* TODO: We should actually stop here... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Missing tcptype info for the TCP candidate!\n", handle->handle_id); } JANUS_LOG(LOG_VERB, "[%"SCNu64"] Transport: TCP (%s)\n", handle->handle_id, type); c->transport = ctype; } #endif strncpy(c->foundation, rfoundation, NICE_CANDIDATE_MAX_FOUNDATION); c->priority = rpriority; nice_address_set_from_string(&c->addr, rip); nice_address_set_port(&c->addr, rport); c->username = g_strdup(stream->ruser); c->password = g_strdup(stream->rpass); if(c->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE || c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) { nice_address_set_from_string(&c->base_addr, rrelip); nice_address_set_port(&c->base_addr, rrelport); } else if(c->type == NICE_CANDIDATE_TYPE_RELAYED) { /* FIXME Do we really need the base address for TURN? */ nice_address_set_from_string(&c->base_addr, rrelip); nice_address_set_port(&c->base_addr, rrelport); } component->candidates = g_slist_append(component->candidates, c); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Candidate added to the list! (%u elements for %d/%d)\n", handle->handle_id, g_slist_length(component->candidates), stream->stream_id, component->component_id); /* Save for the summary, in case we need it */ component->remote_candidates = g_slist_append(component->remote_candidates, g_strdup(candidate)); if(trickle) { if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START)) { /* This is a trickle candidate and ICE has started, we should process it right away */ if(!component->process_started) { /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */ JANUS_LOG(LOG_INFO, "[%"SCNu64"] ICE already started for this component, setting candidates we have up to now\n", handle->handle_id); janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id); } else { GSList *candidates = NULL; candidates = g_slist_append(candidates, c); if(nice_agent_set_remote_candidates(handle->agent, stream->stream_id, component->component_id, candidates) < 1) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to add trickle candidate :-(\n", handle->handle_id); } else { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Trickle candidate added!\n", handle->handle_id); } g_slist_free(candidates); } } else { /* ICE hasn't started yet: to make sure we're not stuck, also check if we stopped processing the SDP */ if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START); /* This is a trickle candidate and ICE has started, we should process it right away */ if(!component->process_started) { /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */ JANUS_LOG(LOG_INFO, "[%"SCNu64"] SDP processed but ICE not started yet for this component, setting candidates we have up to now\n", handle->handle_id); janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id); } else { GSList *candidates = NULL; candidates = g_slist_append(candidates, c); if(nice_agent_set_remote_candidates(handle->agent, stream->stream_id, component->component_id, candidates) < 1) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to add trickle candidate :-(\n", handle->handle_id); } else { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Trickle candidate added!\n", handle->handle_id); } g_slist_free(candidates); } } else { /* Still processing the offer/answer: queue the trickle candidate for now, we'll process it later */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Queueing trickle candidate, status is not START yet\n", handle->handle_id); } } } } } } else { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate (res=%d)...\n", handle->handle_id, res); janus_mutex_unlock(&handle->mutex); return res; } janus_mutex_unlock(&handle->mutex); return 0; }
void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len) { if(dtls == NULL) { JANUS_LOG(LOG_ERR, "No DTLS-SRTP stack, no incoming message...\n"); return; } janus_ice_component *component = (janus_ice_component *)dtls->component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No component, no DTLS...\n"); return; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No stream, no DTLS...\n"); return; } janus_ice_handle *handle = stream->handle; if(!handle || !handle->agent) { JANUS_LOG(LOG_ERR, "No handle/agent, no DTLS...\n"); return; } if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Alert already triggered, clearing up...\n", handle->handle_id); return; } if(!dtls->ssl || !dtls->read_bio) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No DTLS stuff for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); return; } if(dtls->dtls_started == 0) { /* Handshake not started yet: maybe we're still waiting for the answer and the DTLS role? */ return; } janus_dtls_fd_bridge(dtls); int written = BIO_write(dtls->read_bio, buf, len); if(written != len) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Only written %d/%d of those bytes on the read BIO...\n", handle->handle_id, written, len); } else { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Written %d bytes on the read BIO...\n", handle->handle_id, written); } janus_dtls_fd_bridge(dtls); /* Try to read data */ char data[1500]; /* FIXME */ memset(&data, 0, 1500); int read = SSL_read(dtls->ssl, &data, 1500); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] ... and read %d of them from SSL...\n", handle->handle_id, read); if(read < 0) { unsigned long err = SSL_get_error(dtls->ssl, read); if(err == SSL_ERROR_SSL) { /* Ops, something went wrong with the DTLS handshake */ char error[200]; ERR_error_string_n(ERR_get_error(), error, 200); JANUS_LOG(LOG_ERR, "[%"SCNu64"] Handshake error: %s\n", handle->handle_id, error); return; } } janus_dtls_fd_bridge(dtls); if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_STOP) || janus_is_stopping()) { /* DTLS alert triggered, we should end it here */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Forced to stop it here...\n", handle->handle_id); return; } if(!SSL_is_init_finished(dtls->ssl)) { /* Nothing else to do for now */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Initialization not finished yet...\n", handle->handle_id); return; } if(dtls->ready) { /* There's data to be read? */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Any data available?\n", handle->handle_id); #ifdef HAVE_SCTP if(dtls->sctp != NULL && read > 0) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Sending data (%d bytes) to the SCTP stack...\n", handle->handle_id, read); janus_sctp_data_from_dtls(dtls->sctp, data, read); } #else if(read > 0) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Data available but Data Channels support disabled...\n", handle->handle_id); } #endif } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS established, yay!\n", handle->handle_id); /* Check the remote fingerprint */ X509 *rcert = SSL_get_peer_certificate(dtls->ssl); if(!rcert) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No remote certificate?? (%s)\n", handle->handle_id, ERR_reason_error_string(ERR_get_error())); } else { unsigned int rsize; unsigned char rfingerprint[EVP_MAX_MD_SIZE]; char remote_fingerprint[160]; char *rfp = (char *)&remote_fingerprint; if(stream->remote_hashing && !strcasecmp(stream->remote_hashing, "sha-1")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Computing sha-1 fingerprint of remote certificate...\n", handle->handle_id); X509_digest(rcert, EVP_sha1(), (unsigned char *)rfingerprint, &rsize); } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Computing sha-256 fingerprint of remote certificate...\n", handle->handle_id); X509_digest(rcert, EVP_sha256(), (unsigned char *)rfingerprint, &rsize); } X509_free(rcert); rcert = NULL; unsigned int i = 0; for(i = 0; i < rsize; i++) { g_snprintf(rfp, 4, "%.2X:", rfingerprint[i]); rfp += 3; } *(rfp-1) = 0; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Remote fingerprint (%s) of the client is %s\n", handle->handle_id, stream->remote_hashing ? stream->remote_hashing : "sha-256", remote_fingerprint); if(!strcasecmp(remote_fingerprint, stream->remote_fingerprint ? stream->remote_fingerprint : "(none)")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint is a match!\n", handle->handle_id); dtls->dtls_state = JANUS_DTLS_STATE_CONNECTED; dtls->dtls_connected = janus_get_monotonic_time(); /* Notify event handlers */ janus_dtls_notify_state_change(dtls); } else { /* FIXME NOT a match! MITM? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Fingerprint is NOT a match! got %s, expected %s\n", handle->handle_id, remote_fingerprint, stream->remote_fingerprint); dtls->dtls_state = JANUS_DTLS_STATE_FAILED; /* Notify event handlers */ janus_dtls_notify_state_change(dtls); goto done; } if(dtls->dtls_state == JANUS_DTLS_STATE_CONNECTED) { /* Which SRTP profile is being negotiated? */ SRTP_PROTECTION_PROFILE *srtp_profile = SSL_get_selected_srtp_profile(dtls->ssl); if(srtp_profile == NULL) { /* Should never happen, but just in case... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] No SRTP profile selected...\n", handle->handle_id); dtls->dtls_state = JANUS_DTLS_STATE_FAILED; /* Notify event handlers */ janus_dtls_notify_state_change(dtls); goto done; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] %s\n", handle->handle_id, srtp_profile->name); int key_length = 0, salt_length = 0, master_length = 0; switch(srtp_profile->id) { case SRTP_AES128_CM_SHA1_80: case SRTP_AES128_CM_SHA1_32: key_length = SRTP_MASTER_KEY_LENGTH; salt_length = SRTP_MASTER_SALT_LENGTH; master_length = SRTP_MASTER_LENGTH; break; #ifdef HAVE_SRTP_AESGCM case SRTP_AEAD_AES_256_GCM: key_length = SRTP_AESGCM256_MASTER_KEY_LENGTH; salt_length = SRTP_AESGCM256_MASTER_SALT_LENGTH; master_length = SRTP_AESGCM256_MASTER_LENGTH; break; case SRTP_AEAD_AES_128_GCM: key_length = SRTP_AESGCM128_MASTER_KEY_LENGTH; salt_length = SRTP_AESGCM128_MASTER_SALT_LENGTH; master_length = SRTP_AESGCM128_MASTER_LENGTH; break; #endif default: /* Will never happen? */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Unsupported SRTP profile %lu\n", handle->handle_id, srtp_profile->id); break; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] Key/Salt/Master: %d/%d/%d\n", handle->handle_id, master_length, key_length, salt_length); /* Complete with SRTP setup */ unsigned char material[master_length*2]; unsigned char *local_key, *local_salt, *remote_key, *remote_salt; /* Export keying material for SRTP */ if(!SSL_export_keying_material(dtls->ssl, material, master_length*2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { /* Oops... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, couldn't extract SRTP keying material for component %d in stream %d?? (%s)\n", handle->handle_id, component->component_id, stream->stream_id, ERR_reason_error_string(ERR_get_error())); goto done; } /* Key derivation (http://tools.ietf.org/html/rfc5764#section-4.2) */ if(dtls->dtls_role == JANUS_DTLS_ROLE_CLIENT) { local_key = material; remote_key = local_key + key_length; local_salt = remote_key + key_length; remote_salt = local_salt + salt_length; } else { remote_key = material; local_key = remote_key + key_length; remote_salt = local_key + key_length; local_salt = remote_salt + salt_length; } /* Build master keys and set SRTP policies */ /* Remote (inbound) */ switch(srtp_profile->id) { case SRTP_AES128_CM_SHA1_80: srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->remote_policy.rtp)); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->remote_policy.rtcp)); break; case SRTP_AES128_CM_SHA1_32: srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(dtls->remote_policy.rtp)); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->remote_policy.rtcp)); break; #ifdef HAVE_SRTP_AESGCM case SRTP_AEAD_AES_256_GCM: srtp_crypto_policy_set_aes_gcm_256_16_auth(&(dtls->remote_policy.rtp)); srtp_crypto_policy_set_aes_gcm_256_16_auth(&(dtls->remote_policy.rtcp)); break; case SRTP_AEAD_AES_128_GCM: srtp_crypto_policy_set_aes_gcm_128_16_auth(&(dtls->remote_policy.rtp)); srtp_crypto_policy_set_aes_gcm_128_16_auth(&(dtls->remote_policy.rtcp)); break; #endif default: /* Will never happen? */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Unsupported SRTP profile %s\n", handle->handle_id, srtp_profile->name); break; } dtls->remote_policy.ssrc.type = ssrc_any_inbound; unsigned char remote_policy_key[master_length]; dtls->remote_policy.key = (unsigned char *)&remote_policy_key; memcpy(dtls->remote_policy.key, remote_key, key_length); memcpy(dtls->remote_policy.key + key_length, remote_salt, salt_length); #if HAS_DTLS_WINDOW_SIZE dtls->remote_policy.window_size = 128; dtls->remote_policy.allow_repeat_tx = 0; #endif dtls->remote_policy.next = NULL; /* Local (outbound) */ switch(srtp_profile->id) { case SRTP_AES128_CM_SHA1_80: srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->local_policy.rtp)); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->local_policy.rtcp)); break; case SRTP_AES128_CM_SHA1_32: srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(dtls->local_policy.rtp)); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(dtls->local_policy.rtcp)); break; #ifdef HAVE_SRTP_AESGCM case SRTP_AEAD_AES_256_GCM: srtp_crypto_policy_set_aes_gcm_256_16_auth(&(dtls->local_policy.rtp)); srtp_crypto_policy_set_aes_gcm_256_16_auth(&(dtls->local_policy.rtcp)); break; case SRTP_AEAD_AES_128_GCM: srtp_crypto_policy_set_aes_gcm_128_16_auth(&(dtls->local_policy.rtp)); srtp_crypto_policy_set_aes_gcm_128_16_auth(&(dtls->local_policy.rtcp)); break; #endif default: /* Will never happen? */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Unsupported SRTP profile %s\n", handle->handle_id, srtp_profile->name); break; } dtls->local_policy.ssrc.type = ssrc_any_outbound; unsigned char local_policy_key[master_length]; dtls->local_policy.key = (unsigned char *)&local_policy_key; memcpy(dtls->local_policy.key, local_key, key_length); memcpy(dtls->local_policy.key + key_length, local_salt, salt_length); #if HAS_DTLS_WINDOW_SIZE dtls->local_policy.window_size = 128; dtls->local_policy.allow_repeat_tx = 0; #endif dtls->local_policy.next = NULL; /* Create SRTP sessions */ srtp_err_status_t res = srtp_create(&(dtls->srtp_in), &(dtls->remote_policy)); if(res != srtp_err_status_ok) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating inbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_srtp_error_str(res)); goto done; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created inbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); res = srtp_create(&(dtls->srtp_out), &(dtls->local_policy)); if(res != srtp_err_status_ok) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating outbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_srtp_error_str(res)); goto done; } dtls->srtp_profile = srtp_profile->id; dtls->srtp_valid = 1; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created outbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); #ifdef HAVE_SCTP if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS)) { /* Create SCTP association as well */ janus_dtls_srtp_create_sctp(dtls); } #endif dtls->ready = 1; } done: if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && dtls->srtp_valid) { /* Handshake successfully completed */ janus_ice_dtls_handshake_done(handle, component); } else { /* Something went wrong in either DTLS or SRTP... tell the plugin about it */ janus_dtls_callback(dtls->ssl, SSL_CB_ALERT, 0); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING); } } } }