diff --git a/conf/janus.plugin.sip.jcfg.sample b/conf/janus.plugin.sip.jcfg.sample index 06b9b4a5c8..f9f0c38f96 100644 --- a/conf/janus.plugin.sip.jcfg.sample +++ b/conf/janus.plugin.sip.jcfg.sample @@ -52,4 +52,17 @@ general: { # engine (default 32000 milliseconds) sip_timer_t1x64 = 32000 + # In case you want to configure the SIP plugin to create a trunk with + # an external provider or PBX (e.g., to automatically receive all + # INVITEs meant for a range of addresses without the need for users + # to perform any REGISTER), uncomment and fill the following section + # with the related configuration info. Notice that, in order to have + # users use the trunk, you'll need to use the 'trunk' register type: + # regular REGISTER won't work with the trunk. See the documentation + # for more info on what this means and how it works. + #sip_trunk = { + # local = "0.0.0.0:5090" + # peer = "1.2.3.4:5060" + #} + } diff --git a/html/demos/sip.html b/html/demos/sip.html index f69749e78b..556c3533ec 100644 --- a/html/demos/sip.html +++ b/html/demos/sip.html @@ -108,6 +108,7 @@

Demo details

Register using plain secret Register using HA1 secret Register as a guest (no secret) + Register as trunk user (no secret) diff --git a/html/demos/sip.js b/html/demos/sip.js index b00f9a810e..a21a38655c 100644 --- a/html/demos/sip.js +++ b/html/demos/sip.js @@ -57,7 +57,7 @@ $(document).ready(function() { $('.dropdown-toggle').dropdown('hide'); selectedApproach = $(this).attr("id"); $('#registerset').html($(this).html()).parent().removeClass('open'); - if(selectedApproach === "guest") { + if(selectedApproach === "guest" || selectedApproach === "trunk") { $('#password').empty().attr('disabled', true); } else { $('#password').removeAttr('disabled'); @@ -70,7 +70,10 @@ $(document).ready(function() { bootbox.alert("Using this approach might not work with Asterisk because the generated HA1 secret could have the wrong realm"); break; case "guest": - bootbox.alert("Using this approach you'll try to REGISTER as a guest, that is without providing any secret"); + bootbox.alert("Using this approach you'll be marked as a guest, so no REGISTER will be sent"); + break; + case "trunk": + bootbox.alert("Using this approach you'll be associated with a trunk (if available), so no REGISTER will be sent"); break; default: break; @@ -751,20 +754,22 @@ function registerUsername() { $('#registerset').removeAttr('disabled'); return; } - if(selectedApproach === "guest") { - // We're registering as guests, no username/secret provided + if(selectedApproach === "guest" || selectedApproach === "trunk") { + // We're registering as guests or trunk users, no username/secret provided let register = { request: "register", - type: "guest" + type: selectedApproach }; if(sipserver !== "") { register["proxy"] = sipserver; - // Uncomment this if you want to see an outbound proxy too + // Uncomment this if you want to see an outbound proxy too; + // notice that it will be ignored for trunk users (the trunk + // peer will be automatically marked as the outbound proxy to use) //~ register["outbound_proxy"] = "sip:outbound.example.com"; } let username = $('#username').val(); if(!username === "" || username.indexOf("sip:") != 0 || username.indexOf("@") < 0) { - bootbox.alert("Please insert a valid SIP address (e.g., sip:goofy@example.com): this doesn't need to exist for guests, but is required"); + bootbox.alert("Please insert a valid SIP address (e.g., sip:goofy@example.com): this doesn't need to exist for " + selectedApproach + "s, but is required"); $('#server').removeAttr('disabled'); $('#username').removeAttr('disabled'); $('#authuser').removeAttr('disabled'); @@ -778,23 +783,7 @@ function registerUsername() { if(displayname) { register.display_name = displayname; } - if(sipserver === "") { - bootbox.confirm("You didn't specify a SIP Registrar to use: this will cause the plugin to try and conduct a standard (RFC3263) lookup. If this is not what you want or you don't know what this means, hit Cancel and provide a SIP Registrar instead'", - function(result) { - if(result) { - sipcall.send({ message: register }); - } else { - $('#server').removeAttr('disabled'); - $('#username').removeAttr('disabled'); - $('#authuser').removeAttr('disabled'); - $('#displayname').removeAttr('disabled'); - $('#register').removeAttr('disabled').click(registerUsername); - $('#registerset').removeAttr('disabled'); - } - }); - } else { - sipcall.send({ message: register }); - } + sipcall.send({ message: register }); return; } let username = $('#username').val(); diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index cf54432b64..f601e80a17 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -102,7 +102,12 @@ * receive calls unless peers know what your private SIP address is. A SIP * REGISTER isn't sent also when registering as a \c helper : as we'll * explain later, \c helper sessions are sessions only meant to facilitate - * the setup of \ref sipmc. + * the setup of \ref sipmc. Finally, if the SIP plugin is configured with + * support for a trunk, you can register as a \c trunk user too: in that + * case, just as \c guest you won't need to provide a secret (no SIP REGISTER + * will be sent), but you'll be able to receive calls if the trunk between + * the Janus SIP plugin and the peer is configured to route calls associated + * to your SIP identity to this SIP plugin instance. * * That said, a \c register request has to be formatted as follows: * @@ -121,8 +126,8 @@ "authuser" : "", "display_name" : "", "user_agent" : "", - "proxy" : "", - "outbound_proxy" : "", + "proxy" : "", + "outbound_proxy" : "", "headers" : "", "contact_params" : "", "incoming_header_prefixes" : "", @@ -712,6 +717,7 @@ #include +#include #include #include #include @@ -841,7 +847,7 @@ static struct janus_json_parameter call_parameters[] = { {"srtp_profile", JSON_STRING, 0}, {"autoaccept_reinvites", JANUS_JSON_BOOL, 0}, {"refer_id", JANUS_JSON_INTEGER, 0}, - /* The following are only needed in case "guest" registrations + /* The following are only needed in case "guest" or "trunk" registrations * still need an authenticated INVITE for some reason */ {"secret", JSON_STRING, 0}, {"ha1_secret", JSON_STRING, 0}, @@ -1019,9 +1025,6 @@ struct ssip_s { su_home_t s_home[1]; su_root_t *s_root; nua_t *s_nua; - nua_handle_t *s_nh_r, *s_nh_i, *s_nh_m; - char *contact_header; /* Only needed for Sofia SIP >= 1.13 */ - GHashTable *subscriptions; janus_mutex smutex; struct janus_sip_session *session; }; @@ -1056,6 +1059,9 @@ typedef struct janus_sip_account { char *proxy; char *outbound_proxy; janus_sip_registration_status registration_status; + nua_handle_t *s_nh_r, *s_nh_i, *s_nh_m; + char *contact_header; /* Only needed for Sofia SIP >= 1.13 */ + GHashTable *subscriptions; } janus_sip_account; typedef struct janus_sip_media { @@ -1142,23 +1148,47 @@ typedef struct janus_sip_session { char *hangup_reason_header_cause; GList *incoming_header_prefixes; GList *active_calls; - janus_refcount ref; janus_sip_dtmf latest_dtmf; + /* For sessions that may be using a trunk via Janus */ + struct janus_sip_trunk *trunk; + GQueue *trunk_events; + janus_mutex trunk_cond_mutex; + janus_condition trunk_cond; + janus_refcount ref; } janus_sip_session; +/* SIP trunk implementation, if needed */ +typedef struct janus_sip_trunk { + janus_sip_session *session; /* Fake session associated with the trunk (for the SIP stack) */ + char *local; /* Local SIP endpoint */ + char *peer; /* Peer SIP endpoint */ + GHashTable *sessions; /* User sessions associated with this trunk, indexed by username */ + GHashTable *sessions_bynh; /* User sessions associated with this trunk, indexed by NUA handle */ + janus_mutex mutex; +} janus_sip_trunk; + +/* Events to push to users originated by trunk stack */ +typedef struct janus_sip_trunk_event { + json_t *msg, *jsep; +} janus_sip_trunk_event; +static janus_sip_trunk_event janus_sip_trunk_event_quit; + typedef struct janus_sip_call { janus_sip_session *caller; janus_sip_session *callee; } janus_sip_call; -static GHashTable *sessions; -static GHashTable *identities; -static GHashTable *callids; -static GHashTable *messageids; -static GHashTable *masters; -static GHashTable *transfers; +static GHashTable *sessions = NULL; +static GHashTable *identities = NULL; +static GHashTable *callids = NULL; +static GHashTable *messageids = NULL; +static GHashTable *masters = NULL; +static GHashTable *transfers = NULL; + static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER; +static janus_sip_trunk *sip_trunk = NULL; + static void janus_sip_srtp_cleanup(janus_sip_session *session); static void janus_sip_media_reset(janus_sip_session *session); static void janus_sip_rtcp_pli_send(janus_sip_session *session); @@ -1204,9 +1234,9 @@ static void janus_sip_session_dereference(janus_sip_session *session) { static char *janus_sip_session_contact_header_retrieve(janus_sip_session *session) { if(session->helper && session->master) - return session->master->stack->contact_header; + return session->master->account.contact_header; else - return session->stack->contact_header; + return session->account.contact_header; } static void janus_sip_session_free(const janus_refcount *session_ref) { @@ -1218,13 +1248,13 @@ static void janus_sip_session_free(const janus_refcount *session_ref) { g_free(session->account.identity); session->account.identity = NULL; } - if(session->stack != NULL) { + if(session->stack != NULL && session->trunk == NULL) { su_home_deinit(session->stack->s_home); su_home_unref(session->stack->s_home); - g_free(session->stack->contact_header); g_free(session->stack); session->stack = NULL; } + g_free(session->account.contact_header); if(session->account.proxy) { g_free(session->account.proxy); session->account.proxy = NULL; @@ -1333,6 +1363,33 @@ static void janus_sip_transfer_destroy(janus_sip_transfer *t) { g_free(t); } +static void janus_sip_trunk_destroy(janus_sip_trunk *t) { + if(t == NULL) + return; + janus_sip_session_destroy(t->session); + g_free(t->local); + g_free(t->peer); + g_hash_table_destroy(t->sessions); + g_hash_table_destroy(t->sessions_bynh); + g_free(t); +} + +static janus_sip_trunk_event *janus_sip_trunk_event_create(json_t *msg, json_t *jsep) { + janus_sip_trunk_event *te = g_malloc(sizeof(janus_sip_trunk_event)); + te->msg = msg; + te->jsep = jsep; + return te; +} +static void janus_sip_trunk_event_destroy(janus_sip_trunk_event *te) { + if(te == NULL || te == &janus_sip_trunk_event_quit) + return; + if(te->msg) + json_decref(te->msg); + if(te->jsep) + json_decref(te->jsep); + g_free(te); +} + /* SRTP stuff (in case we need SDES) */ static int janus_sip_srtp_set_local(janus_sip_session *session, gboolean video, char **profile, char **crypto) { if(session == NULL) @@ -2132,6 +2189,59 @@ int janus_sip_init(janus_callbacks *callback, const char *config_path) { JANUS_LOG(LOG_VERB, "Sofia SIP certificates folder: %s\n", sips_certs_dir); } + /* Finally, let's check if we need to setup a trunk */ + janus_config_category *cat = janus_config_get(config, config_general, janus_config_type_category, "sip_trunk"); + if(cat && cat->list) { + janus_config_item *lt = janus_config_get(config, cat, janus_config_type_item, "local"); + janus_config_item *pt = janus_config_get(config, cat, janus_config_type_item, "peer"); + if(lt && lt->value && pt && pt->value) { + JANUS_LOG(LOG_INFO, "Creating trunk: %s <--> %s\n", lt->value, pt->value); + /* Allocate the trunk structure */ + sip_trunk = g_malloc(sizeof(janus_sip_trunk)); + sip_trunk->local = g_strdup(lt->value); + sip_trunk->peer = g_strdup(pt->value); + sip_trunk->sessions = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)g_free, (GDestroyNotify)janus_sip_session_dereference); + sip_trunk->sessions_bynh = g_hash_table_new_full(NULL, NULL, + NULL, (GDestroyNotify)janus_sip_session_dereference); + janus_mutex_init(&sip_trunk->mutex); + janus_sip_session *session = g_malloc0(sizeof(janus_sip_session)); + session->trunk = sip_trunk; + session->account.username = g_strdup("janus-trunk"); + janus_mutex_init(&session->rec_mutex); + janus_mutex_init(&session->mutex); + janus_refcount_init(&session->ref, janus_sip_session_free); + sip_trunk->session = session; + /* Start the stack thread */ + GError *error = NULL; + char tname[16]; + g_snprintf(tname, sizeof(tname), "sip trunk"); + janus_refcount_increase(&session->ref); + g_thread_try_new(tname, janus_sip_sofia_thread, session, &error); + if(error != NULL) { + janus_refcount_decrease(&session->ref); + JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SIP Sofia trunk thread...\n", + error->code, error->message ? error->message : "??"); + janus_sip_trunk_destroy(sip_trunk); + sip_trunk = NULL; + } else { + long int timeout = 0; + while(session->stack == NULL || session->stack->s_nua == NULL) { + g_usleep(100000); + timeout += 100000; + if(timeout >= 2000000) { + break; + } + } + if(session == NULL || session->stack == NULL) { + JANUS_LOG(LOG_ERR, "Missing session or Sofia trunk stack\n"); + janus_sip_trunk_destroy(sip_trunk); + sip_trunk = NULL; + } + } + } + } + janus_config_destroy(config); } config = NULL; @@ -2255,6 +2365,17 @@ void janus_sip_destroy(void) { identities = NULL; masters = NULL; transfers = NULL; + /* Get rid of the trunk, if any */ + if(sip_trunk != NULL) { + /* Shutdown the trunk NUA */ + if(sip_trunk->session && sip_trunk->session->stack) { + janus_mutex_lock(&sip_trunk->session->stack->smutex); + if(sip_trunk->session->trunk == NULL && sip_trunk->session->stack->s_nua) + nua_shutdown(sip_trunk->session->stack->s_nua); + janus_mutex_unlock(&sip_trunk->session->stack->smutex); + } + janus_sip_trunk_destroy(sip_trunk); + } janus_mutex_unlock(&sessions_mutex); g_async_queue_unref(messages); messages = NULL; @@ -2315,77 +2436,22 @@ void janus_sip_create_session(janus_plugin_session *handle, int *error) { } janus_sip_session *session = g_malloc0(sizeof(janus_sip_session)); session->handle = handle; - session->account.identity = NULL; - session->account.force_udp = FALSE; - session->account.force_tcp = FALSE; - session->account.sips = FALSE; - session->account.rfc2543_cancel = FALSE; - session->account.username = NULL; - session->account.display_name = NULL; - session->account.user_agent = NULL; - session->account.authuser = NULL; - session->account.secret = NULL; session->account.secret_type = janus_sip_secret_type_unknown; - session->account.sip_port = 0; - session->account.proxy = NULL; - session->account.outbound_proxy = NULL; session->account.registration_status = janus_sip_registration_status_unregistered; session->status = janus_sip_call_status_idle; - session->stack = NULL; - session->transaction = NULL; - session->callee = NULL; - session->callid = NULL; - session->sdp = NULL; - session->hangup_reason_header = NULL; - session->hangup_reason_header_protocol = NULL; - session->hangup_reason_header_cause = NULL; - session->media.remote_audio_ip = NULL; - session->media.remote_video_ip = NULL; - session->media.earlymedia = FALSE; - session->media.update = FALSE; session->media.autoaccept_reinvites = TRUE; - session->media.ready = FALSE; - session->media.require_srtp = FALSE; - session->media.has_srtp_local_audio = FALSE; - session->media.has_srtp_local_video = FALSE; - session->media.has_srtp_remote_audio = FALSE; - session->media.has_srtp_remote_video = FALSE; - session->media.srtp_profile = 0; - session->media.audio_srtp_local_profile = NULL; - session->media.audio_srtp_local_crypto = NULL; - session->media.video_srtp_local_profile = NULL; - session->media.video_srtp_local_crypto = NULL; - session->media.on_hold = FALSE; - session->media.has_audio = FALSE; session->media.audio_rtp_fd = -1; session->media.audio_rtcp_fd= -1; - session->media.local_audio_rtp_port = 0; - session->media.remote_audio_rtp_port = 0; - session->media.local_audio_rtcp_port = 0; - session->media.remote_audio_rtcp_port = 0; - session->media.audio_ssrc = 0; - session->media.audio_ssrc_peer = 0; session->media.audio_pt = -1; session->media.opusred_pt = -1; - session->media.audio_pt_name = NULL; session->media.audio_send = TRUE; session->media.audio_recv = TRUE; session->media.hold_audio_dir = JANUS_SDP_SENDONLY; session->media.pre_hold_audio_dir = JANUS_SDP_DEFAULT; - session->media.has_video = FALSE; session->media.video_rtp_fd = -1; session->media.video_rtcp_fd= -1; - session->media.local_video_rtp_port = 0; - session->media.remote_video_rtp_port = 0; - session->media.local_video_rtcp_port = 0; - session->media.remote_video_rtcp_port = 0; - session->media.video_ssrc = 0; - session->media.video_ssrc_peer = 0; - session->media.simulcast_ssrc = 0; session->media.video_pt = -1; - session->media.video_pt_name = NULL; session->media.video_recv = TRUE; - session->media.video_pli_supported = FALSE; session->media.hold_video_dir = JANUS_SDP_SENDONLY; session->media.pre_hold_video_dir = JANUS_SDP_DEFAULT; session->media.video_orientation_extension_id = -1; @@ -2395,16 +2461,11 @@ void janus_sip_create_session(janus_plugin_session *handle, int *error) { janus_rtp_switching_context_reset(&session->media.vcontext); session->media.pipefd[0] = -1; session->media.pipefd[1] = -1; - session->media.updated = FALSE; session->media.audio_remote_policy.ssrc.type = ssrc_any_inbound; session->media.audio_local_policy.ssrc.type = ssrc_any_inbound; session->media.video_remote_policy.ssrc.type = ssrc_any_inbound; session->media.video_local_policy.ssrc.type = ssrc_any_inbound; janus_mutex_init(&session->rec_mutex); - g_atomic_int_set(&session->establishing, 0); - g_atomic_int_set(&session->established, 0); - g_atomic_int_set(&session->hangingup, 0); - g_atomic_int_set(&session->destroyed, 0); janus_mutex_init(&session->mutex); handle->plugin_handle = session; janus_refcount_init(&session->ref, janus_sip_session_free); @@ -2473,10 +2534,19 @@ void janus_sip_destroy_session(janus_plugin_session *handle, int *error) { /* Shutdown the NUA */ if(session->stack) { janus_mutex_lock(&session->stack->smutex); - if(session->stack->s_nua) + if(session->trunk == NULL && session->stack->s_nua) nua_shutdown(session->stack->s_nua); janus_mutex_unlock(&session->stack->smutex); } + /* For trunk users, we queue a quit message on the queue */ + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, &janus_sip_trunk_event_quit); + janus_condition_signal(&session->trunk_cond); + } + janus_mutex_unlock(&session->trunk_cond_mutex); + } g_hash_table_remove(sessions, handle); janus_mutex_unlock(&sessions_mutex); return; @@ -2877,7 +2947,7 @@ static void janus_sip_hangup_media_internal(janus_plugin_session *handle) { } /* Involve SIP if needed */ janus_mutex_lock(&session->mutex); - if(session->stack->s_nh_i != NULL && session->callee != NULL) { + if(session->account.s_nh_i != NULL && session->callee != NULL) { g_free(session->callee); session->callee = NULL; janus_mutex_unlock(&session->mutex); @@ -2890,9 +2960,9 @@ static void janus_sip_hangup_media_internal(janus_plugin_session *handle) { /* Send a BYE or respond with 480 */ if(janus_sip_call_is_established(session) || session->status == janus_sip_call_status_inviting) - nua_bye(session->stack->s_nh_i, TAG_END()); + nua_bye(session->account.s_nh_i, TAG_END()); else - nua_respond(session->stack->s_nh_i, 480, sip_status_phrase(480), TAG_END()); + nua_respond(session->account.s_nh_i, 480, sip_status_phrase(480), TAG_END()); janus_sip_call_update_status(session, janus_sip_call_status_closing); @@ -2982,13 +3052,22 @@ static void *janus_sip_handler(void *data) { goto error; } /* Parse the request */ - gboolean guest = FALSE, helper = FALSE; + gboolean guest = FALSE, trunk_user = FALSE, helper = FALSE; json_t *type = json_object_get(root, "type"); if(type != NULL) { const char *type_text = json_string_value(type); if(!strcmp(type_text, "guest")) { JANUS_LOG(LOG_INFO, "Registering as a guest\n"); guest = TRUE; + } else if(!strcmp(type_text, "trunk")) { + if(sip_trunk == NULL) { + JANUS_LOG(LOG_ERR, "Can't register as trunk user: no trunk available\n"); + error_code = JANUS_SIP_ERROR_INVALID_ELEMENT; + g_snprintf(error_cause, 512, "Can't register as trunk user: no trunk available"); + goto error; + } + JANUS_LOG(LOG_INFO, "Registering as a trunk user\n"); + trunk_user = TRUE; } else if(!strcmp(type_text, "helper")) { JANUS_LOG(LOG_INFO, "Registering as a helper\n"); helper = TRUE; @@ -3037,8 +3116,8 @@ static void *janus_sip_handler(void *data) { if(session->stack == NULL) { session->stack = g_malloc0(sizeof(ssip_t)); su_home_init(session->stack->s_home); - if(session->master->stack->contact_header != NULL) - session->stack->contact_header = g_strdup(session->master->stack->contact_header); + if(session->master->account.contact_header != NULL) + session->account.contact_header = g_strdup(session->master->account.contact_header); } /* Check if custom headers need to be intercepted */ json_t *header_prefixes_json = json_object_get(root, "incoming_header_prefixes"); @@ -3134,7 +3213,7 @@ static void *janus_sip_handler(void *data) { /* Parse addresses */ json_t *proxy = json_object_get(root, "proxy"); const char *proxy_text = NULL; - if(proxy && !json_is_null(proxy)) { + if(!trunk_user && proxy && !json_is_null(proxy)) { /* Has to be validated separately because it could be null */ JANUS_VALIDATE_JSON_OBJECT(root, proxy_parameters, error_code, error_cause, TRUE, @@ -3152,7 +3231,7 @@ static void *janus_sip_handler(void *data) { } json_t *outbound_proxy = json_object_get(root, "outbound_proxy"); const char *obproxy_text = NULL; - if(outbound_proxy && !json_is_null(outbound_proxy)) { + if(!trunk_user && outbound_proxy && !json_is_null(outbound_proxy)) { /* Has to be validated separately because it could be null */ JANUS_VALIDATE_JSON_OBJECT(root, proxy_parameters, error_code, error_cause, TRUE, @@ -3215,8 +3294,21 @@ static void *janus_sip_handler(void *data) { g_strlcpy(user_id, username_uri.url->url_user, sizeof(user_id)); if(guest) { /* Not needed, we can stop here: just say we're registered */ - JANUS_LOG(LOG_INFO, "Guest will have username %s\n", user_id); + JANUS_LOG(LOG_INFO, "Guest will have username '%s'\n", user_id); send_register = FALSE; + } else if(trunk_user) { + /* Same thing here, no actual registration needed: just + * keep track of this session in the trunk, and enforce + * the trunk peer as the outbound proxy for outgoing stuff */ + JANUS_LOG(LOG_INFO, "Trunk user will have username '%s'\n", user_id); + send_register = FALSE; + char trunk_peer[1024]; + g_snprintf(trunk_peer, sizeof(trunk_peer), "sip:%s", sip_trunk->peer); + obproxy_text = g_strdup(trunk_peer); + janus_mutex_lock(&sip_trunk->mutex); + session->trunk = sip_trunk; + g_hash_table_insert(sip_trunk->sessions, g_strdup(user_id), session); + janus_mutex_unlock(&sip_trunk->mutex); } else { json_t *secret = json_object_get(root, "secret"); json_t *ha1_secret = json_object_get(root, "ha1_secret"); @@ -3333,7 +3425,7 @@ static void *janus_sip_handler(void *data) { session->account.proxy = g_strdup(proxy_text); } if(obproxy_text) { - session->account.outbound_proxy = g_strdup(obproxy_text); + session->account.outbound_proxy = trunk_user ? (char *)obproxy_text : g_strdup(obproxy_text); } session->account.registration_status = janus_sip_registration_status_registering; @@ -3355,7 +3447,7 @@ static void *janus_sip_handler(void *data) { goto error; } long int timeout = 0; - while(session->stack == NULL || session->stack->s_nua == NULL) { + while(session->stack == NULL || (!trunk_user && session->stack->s_nua == NULL)) { g_usleep(100000); timeout += 100000; if(timeout >= 2000000) { @@ -3369,15 +3461,15 @@ static void *janus_sip_handler(void *data) { goto error; } } - if(session == NULL || session->stack == NULL) { + if(session == NULL || (!trunk_user && session->stack == NULL)) { JANUS_LOG(LOG_ERR, "Missing session or Sofia stack\n"); error_code = JANUS_SIP_ERROR_UNKNOWN_ERROR; g_snprintf(error_cause, 512, "Missing session or Sofia stack"); goto error; } - if(session->stack->s_nh_r != NULL) { - nua_handle_destroy(session->stack->s_nh_r); - session->stack->s_nh_r = NULL; + if(session->account.s_nh_r != NULL) { + nua_handle_destroy(session->account.s_nh_r); + session->account.s_nh_r = NULL; } if(send_register) { @@ -3396,9 +3488,9 @@ static void *janus_sip_handler(void *data) { g_snprintf(error_cause, 512, "Invalid NUA"); goto error; } - session->stack->s_nh_r = nua_handle(session->stack->s_nua, session, TAG_END()); + session->account.s_nh_r = nua_handle(session->stack->s_nua, session, TAG_END()); janus_mutex_unlock(&session->stack->smutex); - if(session->stack->s_nh_r == NULL) { + if(session->account.s_nh_r == NULL) { JANUS_LOG(LOG_ERR, "NUA Handle for REGISTER still null??\n"); error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; g_snprintf(error_cause, 512, "Invalid NUA Handle"); @@ -3408,7 +3500,7 @@ static void *janus_sip_handler(void *data) { char ttl_text[20]; g_snprintf(ttl_text, sizeof(ttl_text), "%d", ttl); /* Send the REGISTER */ - nua_register(session->stack->s_nh_r, + nua_register(session->account.s_nh_r, NUTAG_M_USERNAME(session->account.authuser), NUTAG_M_DISPLAY(session->account.display_name), SIPTAG_FROM_STR(username_text), @@ -3472,7 +3564,7 @@ static void *janus_sip_handler(void *data) { g_snprintf(error_cause, 512, "Wrong state (not registered)"); goto error; } - if(session->stack->s_nh_r == NULL) { + if(session->account.s_nh_r == NULL) { JANUS_LOG(LOG_ERR, "NUA Handle for REGISTER still null??\n"); error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; g_snprintf(error_cause, 512, "Invalid NUA Handle"); @@ -3480,7 +3572,7 @@ static void *janus_sip_handler(void *data) { } /* Unregister now */ session->account.registration_status = janus_sip_registration_status_unregistering; - nua_unregister(session->stack->s_nh_r, TAG_END()); + nua_unregister(session->account.s_nh_r, TAG_END()); result = json_object(); json_object_set_new(result, "event", json_string("unregistering")); } else if(!strcasecmp(request_text, "subscribe")) { @@ -3522,8 +3614,8 @@ static void *janus_sip_handler(void *data) { /* Do we have a handle for this subscription already? */ janus_mutex_lock(&session->stack->smutex); nua_handle_t *nh = NULL; - if(session->stack->subscriptions != NULL) - nh = g_hash_table_lookup(session->stack->subscriptions, (char *)event_type); + if(session->account.subscriptions != NULL) + nh = g_hash_table_lookup(session->account.subscriptions, (char *)event_type); if(nh == NULL) { /* We don't, create one now */ if(!session->helper) { @@ -3553,18 +3645,25 @@ static void *janus_sip_handler(void *data) { nh = nua_handle(session->master->stack->s_nua, session, TAG_END()); janus_mutex_unlock(&session->master->stack->smutex); } - if(session->stack->subscriptions == NULL) { + if(session->account.subscriptions == NULL) { /* We still need a table for mapping these subscriptions as well */ - session->stack->subscriptions = g_hash_table_new_full(g_int64_hash, g_int64_equal, + session->account.subscriptions = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, (GDestroyNotify)nua_handle_destroy); } - g_hash_table_insert(session->stack->subscriptions, g_strdup(event_type), nh); + g_hash_table_insert(session->account.subscriptions, g_strdup(event_type), nh); } janus_mutex_unlock(&session->stack->smutex); char custom_headers[2048]; janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); /* Retrieve the Contact header for manually adding if not NULL */ char *contact_header = janus_sip_session_contact_header_retrieve(session); + if(session->trunk) { + /* Associate this NUA handle to this trunk user */ + janus_mutex_lock(&sip_trunk->mutex); + janus_refcount_increase(&session->ref); + g_hash_table_insert(sip_trunk->sessions_bynh, nh, session); + janus_mutex_unlock(&sip_trunk->mutex); + } /* Send the SUBSCRIBE */ nua_subscribe(nh, SIPTAG_TO_STR(to), @@ -3602,8 +3701,8 @@ static void *janus_sip_handler(void *data) { /* Get the handle we used for this subscription */ janus_mutex_lock(&session->stack->smutex); nua_handle_t *nh = NULL; - if(session->stack->subscriptions != NULL) - nh = g_hash_table_lookup(session->stack->subscriptions, (char *)event_type); + if(session->account.subscriptions != NULL) + nh = g_hash_table_lookup(session->account.subscriptions, (char *)event_type); janus_mutex_unlock(&session->stack->smutex); if(nh == NULL) { JANUS_LOG(LOG_ERR, "Wrong state (not subscribed to this event)\n"); @@ -3792,8 +3891,14 @@ static void *janus_sip_handler(void *data) { char *local_tag = g_malloc0(7); janus_sip_random_string(7, local_tag); /* Prepare the stack */ - if(session->stack->s_nh_i != NULL) - nua_handle_destroy(session->stack->s_nh_i); + if(session->account.s_nh_i != NULL) { + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk->mutex); + g_hash_table_remove(session->trunk->sessions_bynh, session->account.s_nh_i); + janus_mutex_unlock(&session->trunk->mutex); + } + nua_handle_destroy(session->account.s_nh_i); + } if(!session->helper) { janus_mutex_lock(&session->stack->smutex); if(session->stack->s_nua == NULL) { @@ -3806,7 +3911,7 @@ static void *janus_sip_handler(void *data) { g_snprintf(error_cause, 512, "Invalid NUA"); goto error; } - session->stack->s_nh_i = nua_handle(session->stack->s_nua, session, TAG_END()); + session->account.s_nh_i = nua_handle(session->stack->s_nua, session, TAG_END()); janus_mutex_unlock(&session->stack->smutex); if(session->account.display_name) { g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>;tag=%s", @@ -3837,7 +3942,7 @@ static void *janus_sip_handler(void *data) { g_snprintf(error_cause, 512, "Invalid NUA"); goto error; } - session->stack->s_nh_i = nua_handle(session->master->stack->s_nua, session, TAG_END()); + session->account.s_nh_i = nua_handle(session->master->stack->s_nua, session, TAG_END()); janus_mutex_unlock(&session->master->stack->smutex); if(session->master->account.display_name) { g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>;tag=%s", @@ -3847,7 +3952,7 @@ static void *janus_sip_handler(void *data) { session->master->account.identity, local_tag); } } - if(session->stack->s_nh_i == NULL) { + if(session->account.s_nh_i == NULL) { JANUS_LOG(LOG_WARN, "NUA Handle for INVITE still null??\n"); g_free(local_tag); g_free(sdp); @@ -3971,8 +4076,15 @@ static void *janus_sip_handler(void *data) { janus_sip_ref_active_call(session); /* Retrieve the Contact header for manually adding if not NULL */ char *contact_header = janus_sip_session_contact_header_retrieve(session); + if(session->trunk) { + /* Associate this NUA handle to this trunk user */ + janus_mutex_lock(&sip_trunk->mutex); + janus_refcount_increase(&session->ref); + g_hash_table_insert(sip_trunk->sessions_bynh, session->account.s_nh_i, session); + janus_mutex_unlock(&sip_trunk->mutex); + } /* Send the INVITE */ - nua_invite(session->stack->s_nh_i, + nua_invite(session->account.s_nh_i, SIPTAG_FROM_STR(from_hdr), SIPTAG_TO_STR(uri_text), SIPTAG_CALL_ID_STR(callid), @@ -4175,11 +4287,11 @@ static void *janus_sip_handler(void *data) { } g_atomic_int_set(&session->hangingup, 0); janus_sip_call_update_status(session, progress ? janus_sip_call_status_progress : janus_sip_call_status_incall); - if(session->stack->s_nh_i == NULL) { + if(session->account.s_nh_i == NULL) { JANUS_LOG(LOG_WARN, "NUA Handle for %s null\n", progress ? "183 Session Progress" : "200 OK"); } int sip_response = progress ? 183 : 200; - nua_respond(session->stack->s_nh_i, + nua_respond(session->account.s_nh_i, sip_response, sip_status_phrase(sip_response), SOATAG_USER_SDP_STR(sdp), SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON), @@ -4352,13 +4464,13 @@ static void *janus_sip_handler(void *data) { /* Retrieve the Contact header for manually adding if not NULL */ char *contact_header = janus_sip_session_contact_header_retrieve(session); /* We're sending a re-INVITE ourselves */ - nua_invite(session->stack->s_nh_i, + nua_invite(session->account.s_nh_i, TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)), SOATAG_USER_SDP_STR(sdp), TAG_END()); } else { /* We're answering to a re-INVITE we received */ - nua_respond(session->stack->s_nh_i, + nua_respond(session->account.s_nh_i, 200, sip_status_phrase(200), SOATAG_USER_SDP_STR(sdp), SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON), @@ -4450,7 +4562,7 @@ static void *janus_sip_handler(void *data) { session->media.ready = FALSE; session->media.on_hold = FALSE; janus_sip_call_update_status(session, janus_sip_call_status_closing); - if(session->stack->s_nh_i == NULL) { + if(session->account.s_nh_i == NULL) { JANUS_LOG(LOG_WARN, "NUA Handle for 200 OK still null??\n"); } int response_code = 603; @@ -4464,7 +4576,7 @@ static void *janus_sip_handler(void *data) { /* Check if the response needs to be enriched with custom headers */ char custom_headers[2048]; janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); - nua_respond(session->stack->s_nh_i, response_code, sip_status_phrase(response_code), + nua_respond(session->account.s_nh_i, response_code, sip_status_phrase(response_code), TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), TAG_END()); janus_mutex_lock(&session->mutex); @@ -4530,14 +4642,14 @@ static void *janus_sip_handler(void *data) { if(callid != NULL) { /* This is an attended transfer, make sure this call exists */ janus_sip_session *replaced = janus_sip_active_call_from_callid(session, callid); - if(replaced == NULL || replaced->stack == NULL || replaced->stack->s_nh_i == NULL) { + if(replaced == NULL || replaced->stack == NULL || replaced->account.s_nh_i == NULL) { JANUS_LOG(LOG_ERR, "No such call-ID %s\n", callid); error_code = JANUS_SIP_ERROR_NO_SUCH_CALLID; g_snprintf(error_cause, 512, "No such call-ID %s", callid); goto error; } /* Craft the Replaces header field */ - sip_replaces_t *r = nua_handle_make_replaces(replaced->stack->s_nh_i, session->stack->s_home, 0); + sip_replaces_t *r = nua_handle_make_replaces(replaced->account.s_nh_i, session->stack->s_home, 0); char *replaces = sip_headers_as_url_query(session->stack->s_home, SIPTAG_REPLACES(r), TAG_END()); refer_to = sip_refer_to_format(session->stack->s_home, "<%s?%s>", uri_text, replaces); JANUS_LOG(LOG_VERB, "Attended transfer: <%s?%s>\n", uri_text, replaces); @@ -4547,7 +4659,7 @@ static void *janus_sip_handler(void *data) { if(refer_to == NULL) refer_to = sip_refer_to_format(session->stack->s_home, "<%s>", uri_text); /* Send the REFER */ - nua_refer(session->stack->s_nh_i, + nua_refer(session->account.s_nh_i, SIPTAG_REFER_TO(refer_to), TAG_END()); @@ -4659,7 +4771,7 @@ static void *janus_sip_handler(void *data) { char *contact_header = janus_sip_session_contact_header_retrieve(session); /* Send the re-INVITE */ char *sdp = janus_sdp_write(session->sdp); - nua_invite(session->stack->s_nh_i, + nua_invite(session->account.s_nh_i, TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)), SOATAG_USER_SDP_STR(sdp), TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), @@ -4695,7 +4807,7 @@ static void *janus_sip_handler(void *data) { janus_sip_call_update_status(session, janus_sip_call_status_closing); char custom_headers[2048]; janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); - nua_bye(session->stack->s_nh_i, + nua_bye(session->account.s_nh_i, TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), TAG_END()); janus_mutex_lock(&session->mutex); @@ -4936,7 +5048,7 @@ static void *janus_sip_handler(void *data) { const char *info_content = json_string_value(json_object_get(root, "content")); char custom_headers[2048]; janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); - nua_info(session->stack->s_nh_i, + nua_info(session->account.s_nh_i, SIPTAG_CONTENT_TYPE_STR(info_type), SIPTAG_PAYLOAD_STR(info_content), TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), @@ -5003,7 +5115,7 @@ static void *janus_sip_handler(void *data) { if(in_dialog_message) { /* Take Call-ID, later used to report delivery status */ message_callid = g_strdup(session->callid) ; - nua_message(session->stack->s_nh_i, + nua_message(session->account.s_nh_i, SIPTAG_CONTENT_TYPE_STR(content_type), SIPTAG_PAYLOAD_STR(msg_content), TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), @@ -5053,6 +5165,13 @@ static void *janus_sip_handler(void *data) { message_callid = g_malloc0(24); janus_sip_random_string(24, message_callid); } + if(session->trunk) { + /* Associate this NUA handle to this trunk user */ + janus_mutex_lock(&sip_trunk->mutex); + janus_refcount_increase(&session->ref); + g_hash_table_insert(sip_trunk->sessions_bynh, nh, session); + janus_mutex_unlock(&sip_trunk->mutex); + } nua_message(nh, SIPTAG_TO_STR(uri_text), SIPTAG_CONTENT_TYPE_STR(content_type), @@ -5114,7 +5233,7 @@ static void *janus_sip_handler(void *data) { g_snprintf(payload, sizeof(payload), "Signal=%s\r\nDuration=%d", digit_text, duration_ms); char custom_headers[2048]; janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); - nua_info(session->stack->s_nh_i, + nua_info(session->account.s_nh_i, SIPTAG_CONTENT_TYPE_STR("application/dtmf-relay"), SIPTAG_PAYLOAD_STR(payload), TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), @@ -5222,8 +5341,58 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, janus_sip_session *session = (janus_sip_session *)(hmagic ? hmagic : magic); ssip_t *ssip = session->stack; + gboolean is_peer = FALSE; + char *username = session->account.username; + if(sip_trunk && session == sip_trunk->session && event != nua_r_get_params) { + /* This is the trunk SIP stack: let's find the actual session + * this new update is meant for, by NUA or by SIP header info */ + janus_mutex_lock(&sip_trunk->mutex); + janus_sip_session *s = g_hash_table_lookup(sip_trunk->sessions_bynh, nh); + if(s == NULL && ssip) { + /* Couldn't find any session from the NUA handle, let's check + * the source address first, to make sure it's our trunk peer */ + msg_t *msg = nua_current_request(nua); + if(msg) { + char ip[80] = {0}; + uint16_t port = 0; + su_addrinfo_t *addrinfo = msg_addrinfo(msg); + if(addrinfo && addrinfo->ai_addr) { + getnameinfo(addrinfo->ai_addr, addrinfo->ai_addrlen, ip, + (socklen_t)sizeof(ip), NULL, 0, NI_NUMERICHOST); + port = ntohs(((struct sockaddr_in *)addrinfo->ai_addr)->sin_port); + char peer[100] = {0}; + g_snprintf(peer, sizeof(peer), "%s:%"SCNu16, ip, port); + JANUS_LOG(LOG_HUGE, "[%s]: SIP request from %s (expecting: %s)\n", + username, peer, sip_trunk->peer); + if(!strcasecmp(sip_trunk->peer, peer)) + is_peer = TRUE; + } + } + /* Now let's check the To header, to find the session that way */ + if(sip && sip->sip_to && is_peer) { + /* FIXME What should be here? Just the username? The whole address? */ + const char *to = sip->sip_to->a_url[0].url_user; + if(to && strlen(to) > 0) { + JANUS_LOG(LOG_HUGE, "[%s]: Looking up trunk user '%s'\n", username, to); + s = g_hash_table_lookup(sip_trunk->sessions, to); + if(s) { + /* Create the mapping between this session and the NUA handle */ + JANUS_LOG(LOG_HUGE, "[%s]: Trunk user '%s' found! %p\n", username, to, s); + janus_refcount_increase(&s->ref); + g_hash_table_insert(sip_trunk->sessions_bynh, nh, s); + } + } + } + } + /* Note: may be NULL, at this point, so we'll have to be prepared for that */ + session = s; + if(session) + username = session->account.username; + janus_mutex_unlock(&sip_trunk->mutex); + } + /* Notify event handlers about the content of the whole incoming SIP message, if any */ - if(notify_events && gateway->events_is_enabled() && ssip) { + if(notify_events && gateway && gateway->events_is_enabled() && ssip && session) { /* Print the incoming message */ size_t msg_size = 0; msg_t *msg = nua_current_request(nua); @@ -5237,34 +5406,36 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } } - switch (event) { + switch(event) { /* Status or Error Indications */ case nua_i_active: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_i_error: - JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_i_fork: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_i_media_error: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_i_subscription: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; - case nua_i_state:; + case nua_i_state: + if(session == NULL) + break; tagi_t const *ti = tl_find(tags, nutag_callstate); - enum nua_callstate callstate = ti ? ti->t_value : nua_callstate_init; - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s, call state [%s]\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??", nua_callstate_name(callstate)); + enum nua_callstate callstate = ti ? ti->t_value : -1; + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s, call state [%s]\n", username, nua_event_name(event), status, phrase ? phrase : "??", nua_callstate_name(callstate)); /* There are several call states, but we care about the terminated state in order to send the 'hangup' event * and the proceeding state in order to send the 'proceeding' event so the client can play a ringback tone for * the user since we don't send early media. (assuming this is the right session, of course). * http://sofia-sip.sourceforge.net/refdocs/nua/nua__tag_8h.html#a516dc237722dc8ca4f4aa3524b2b444b */ if(callstate == nua_callstate_proceeding && - (session->stack->s_nh_i == nh || session->stack->s_nh_i == NULL)) { + (session->account.s_nh_i == nh || session->account.s_nh_i == NULL)) { json_t *call = json_object(); json_object_set_new(call, "sip", json_string("event")); json_t *calling = json_object(); @@ -5272,9 +5443,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(calling, "code", json_integer(status)); json_object_set_new(call, "result", calling); json_object_set_new(call, "call_id", json_string(session->callid)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(call); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(call, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(call); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(call); + } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); @@ -5285,14 +5468,14 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, gateway->notify_event(&janus_sip_plugin, session->handle, info); } } else if(callstate == nua_callstate_terminated && - (session->stack->s_nh_i == nh || session->stack->s_nh_i == NULL)) { + (session->account.s_nh_i == nh || session->account.s_nh_i == NULL)) { session->media.earlymedia = FALSE; session->media.update = FALSE; session->media.autoaccept_reinvites = TRUE; session->media.ready = FALSE; session->media.on_hold = FALSE; janus_sip_call_update_status(session, janus_sip_call_status_idle); - session->stack->s_nh_i = NULL; + session->account.s_nh_i = NULL; json_t *call = json_object(); json_object_set_new(call, "sip", json_string("event")); json_t *calling = json_object(); @@ -5307,9 +5490,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(calling, "reason_header_cause", json_string(session->hangup_reason_header_cause)); json_object_set_new(call, "result", calling); json_object_set_new(call, "call_id", json_string(session->callid)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(call); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(call, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(call); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(call); + } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); @@ -5357,24 +5552,25 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, /* Also clean up locally, in case there was no PC */ janus_sip_hangup_media_internal(session->handle); } - } else if(session->stack->s_nh_i == nh && callstate == nua_callstate_calling && session->status == janus_sip_call_status_incall) { + } else if(session->account.s_nh_i == nh && callstate == nua_callstate_calling && session->status == janus_sip_call_status_incall) { /* Have just sent re-INVITE */ janus_sip_call_update_status(session, janus_sip_call_status_incall_reinviting); - } else if(session->stack->s_nh_i == nh && callstate == nua_callstate_ready && + } else if(session->account.s_nh_i == nh && callstate == nua_callstate_ready && (session->status == janus_sip_call_status_incall_reinviting || session->status == janus_sip_call_status_incall_reinvited)) { /* Clear re-INVITE progress status */ janus_sip_call_update_status(session, janus_sip_call_status_incall); } break; case nua_i_terminated: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* We had a reference to this session for this call, get rid of it */ - janus_sip_unref_active_call(session); + if(session) + janus_sip_unref_active_call(session); break; } /* SIP requests */ case nua_i_ack: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* We're only interested in this when there's been an offerless INVITE, as here's where we'd get our answer */ if(sip->sip_payload && sip->sip_payload->pl_data) { JANUS_LOG(LOG_VERB, "This ACK contains a payload, probably as a result of an offerless INVITE: simulating 200 OK...\n"); @@ -5383,20 +5579,26 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, break; } case nua_i_outbound: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_i_bye: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); janus_sip_save_reason(sip, session); break; } case nua_i_cancel: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); janus_sip_save_reason(sip, session); break; } case nua_i_invite: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); + if(session == NULL) { + /* The trunk received a call for an unhandled user, reject it */ + int code = is_peer ? 404 : 403; + nua_respond(nh, code, sip_status_phrase(code), TAG_END()); + break; + } /* Add a reference for this call */ janus_sip_ref_active_call(session); if(ssip == NULL) { @@ -5410,13 +5612,13 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, break; } gboolean reinvite = FALSE, busy = FALSE; - if(session->stack->s_nh_i == NULL) { + if(session->account.s_nh_i == NULL) { if(g_atomic_int_get(&session->establishing) || g_atomic_int_get(&session->established) || session->relayer_thread != NULL) { /* Still busy establishing another call (or maybe still cleaning up the previous call) */ busy = TRUE; } } else { - if(session->stack->s_nh_i == nh) { + if(session->account.s_nh_i == nh) { /* re-INVITE, we'll check what changed later */ reinvite = TRUE; JANUS_LOG(LOG_VERB, "Got a re-INVITE...\n"); @@ -5434,7 +5636,7 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, GList *temp = session->helpers; while(temp != NULL) { helper = (janus_sip_session *)temp->data; - if(helper->stack->s_nh_i == NULL && !g_atomic_int_get(&helper->establishing) && + if(helper->account.s_nh_i == NULL && !g_atomic_int_get(&helper->establishing) && !g_atomic_int_get(&helper->established) && helper->relayer_thread == NULL) { /* Found! */ break; @@ -5469,9 +5671,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(result, "callee", json_string(callee_text)); json_object_set_new(missed, "result", result); json_object_set_new(missed, "call_id", json_string(sip->sip_call_id->i_id)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, missed, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(missed); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(missed, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(missed); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, missed, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(missed); + } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); @@ -5618,11 +5832,25 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } json_object_set_new(call, "result", calling); json_object_set_new(call, "call_id", json_string(session->callid)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(call); - if(jsep) - json_decref(jsep); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(call, jsep)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(call); + if(jsep) + json_decref(call); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(call); + if(jsep) + json_decref(jsep); + } janus_sdp_destroy(sdp); /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { @@ -5643,12 +5871,18 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, if(!reinvite) { /* Send a Ringing back */ nua_respond(nh, 180, sip_status_phrase(180), TAG_END()); - session->stack->s_nh_i = nh; + session->account.s_nh_i = nh; } break; } case nua_i_refer: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); + if(session == NULL) { + /* The trunk received a refer for an unhandled user, reject it */ + int code = is_peer ? 404 : 403; + nua_respond(nh, code, sip_status_phrase(code), TAG_END()); + break; + } /* We're being asked to transfer a call */ if(sip == NULL || sip->sip_refer_to == NULL) { JANUS_LOG(LOG_ERR, "Missing Refer-To header\n"); @@ -5728,13 +5962,31 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, if(custom_headers != NULL) su_free(session->stack->s_home, custom_headers); json_object_set_new(info, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(info); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(info, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(info); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(info); + } break; } case nua_i_info: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); + if(session == NULL) { + /* The trunk received a call for an unhandled user, reject it */ + int code = is_peer ? 404 : 403; + nua_respond(nh, code, sip_status_phrase(code), TAG_END()); + break; + } /* We expect a payload */ if(!sip->sip_content_type || !sip->sip_content_type->c_type || !sip->sip_payload || !sip->sip_payload->pl_data) { return; @@ -5761,13 +6013,31 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, if(session->callid) json_object_set_new(info, "call_id", json_string(session->callid)); json_object_set_new(info, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(info); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(info, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(info); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(info); + } break; } case nua_i_message: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); + if(session == NULL) { + /* The trunk received a call for an unhandled user, reject it */ + int code = is_peer ? 404 : 403; + nua_respond(nh, code, sip_status_phrase(code), TAG_END()); + break; + } /* We expect a payload */ if(!sip->sip_content_type || !sip->sip_content_type->c_type || !sip->sip_payload || !sip->sip_payload->pl_data) { return; @@ -5794,13 +6064,31 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(message, "call_id", json_string(session->callid)); json_object_set_new(result, "content_type", json_string(content_type)); json_object_set_new(message, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, message, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(message); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(message, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(message); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, message, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(message); + } break; } case nua_i_notify: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); + if(session == NULL) { + /* The trunk received a call for an unhandled user, reject it */ + int code = is_peer ? 404 : 403; + nua_respond(nh, code, sip_status_phrase(code), TAG_END()); + break; + } /* We expect a payload */ if(!sip) { /* No SIP message? Maybe an internal message? */ @@ -5832,19 +6120,31 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(result, "headers", headers); } json_object_set_new(notify, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, notify, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(notify); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(notify, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(notify); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, notify, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(notify); + } break; } case nua_i_options: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* Stack responds automatically to OPTIONS request unless OPTIONS is * included in the set of application methods, set by NUTAG_APPL_METHOD(). */ break; /* Responses */ case nua_r_get_params: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); const tagi_t* from = NULL; if((status != 200) || ((from = tl_find(tags, siptag_from_str)) == NULL)) { JANUS_LOG(LOG_WARN, "Unable to find 'siptag_from_str' among all the tags\n"); @@ -5856,17 +6156,17 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, break; } JANUS_LOG(LOG_VERB, "'siptag_from_str': %s\n", from_value); - g_free(ssip->contact_header); - ssip->contact_header = g_strdup(from_value); + g_free(session->account.contact_header); + session->account.contact_header = g_strdup(from_value); break; case nua_r_set_params: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_r_notifier: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_r_shutdown: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); if(status < 200 && !g_atomic_int_get(&stopping)) { /* shutdown in progress -> return */ break; @@ -5890,21 +6190,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } break; case nua_r_terminate: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; /* SIP responses */ case nua_r_bye: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_r_cancel: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); break; case nua_r_info: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* FIXME Should we notify the user, in case the SIP INFO returned an error? */ break; case nua_r_message: - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* Handle authentication for SIP MESSAGE - eg. SippySoft Softswitch requires 401 authentication even if SIP user is registered */ if(status == 401 || status == 407) { const char *scheme = NULL; @@ -5983,9 +6283,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(dr, "result", result); json_object_set_new(dr, "call_id", json_string(messageid)); /* Report delivery */ - int ret = gateway->push_event(message_session->handle, &janus_sip_plugin, message_session->transaction, dr, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(dr); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(dr, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(dr); + } + } else { + int ret = gateway->push_event(message_session->handle, &janus_sip_plugin, message_session->transaction, dr, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(dr); + } janus_mutex_lock(&sessions_mutex); g_hash_table_remove(messageids, messageid); janus_mutex_unlock(&sessions_mutex); @@ -5993,13 +6305,13 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } break; case nua_r_refer: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* We got a response to our REFER */ JANUS_LOG(LOG_VERB, "Response to REFER received\n"); break; } case nua_r_invite: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* If this INVITE was triggered by a REFER, notify the transferer */ if(session->refer_id > 0) { @@ -6037,9 +6349,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } json_object_set_new(ringing, "result", result); json_object_set_new(ringing, "call_id", json_string(session->callid)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, ringing, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(ringing); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(ringing, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(ringing); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, ringing, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(ringing); + } break; } } else { @@ -6257,10 +6581,23 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } json_object_set_new(call, "result", calling); json_object_set_new(call, "call_id", json_string(session->callid)); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(call); - json_decref(jsep); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(call, jsep)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(call); + json_decref(jsep); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(call); + json_decref(jsep); + } janus_sdp_destroy(sdp); /* Also notify event handlers */ if(!session->media.update && notify_events && gateway->events_is_enabled()) { @@ -6279,7 +6616,7 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, } case nua_r_register: case nua_r_unregister: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); if(status == 200) { if(event == nua_r_register) { if(session->account.registration_status < janus_sip_registration_status_registered) @@ -6304,9 +6641,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(reging, "headers", headers); } json_object_set_new(reg, "result", reging); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, reg, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(reg); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(reg, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(reg); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, reg, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(reg); + } /* If we unregistered and this session had helpers, get rid of them */ if(event == nua_r_unregister) { janus_mutex_lock(&session->mutex); @@ -6436,9 +6785,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(result, "headers", headers); } json_object_set_new(event, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(event); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(event, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(event); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(event); + } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); @@ -6451,7 +6812,7 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, break; } case nua_r_subscribe: { - JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); if(status == 200 || status == 202) { /* Success */ json_t *event = json_object(); @@ -6468,9 +6829,21 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(result, "expires", json_integer(sip->sip_expires->ex_delta)); json_object_set_new(result, "reason", json_string(phrase ? phrase : "")); json_object_set_new(event, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(event); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(event, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(event); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(event); + } } else if(status == 401 || status == 407) { const char *scheme = NULL; const char *realm = NULL; @@ -6543,14 +6916,26 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_object_set_new(result, "headers", headers); } json_object_set_new(event, "result", result); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); - JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(event); + if(session->trunk != NULL) { + janus_mutex_lock(&session->trunk_cond_mutex); + if(session->trunk_events != NULL) { + g_queue_push_tail(session->trunk_events, janus_sip_trunk_event_create(event, NULL)); + janus_condition_signal(&session->trunk_cond); + janus_mutex_unlock(&session->trunk_cond_mutex); + } else { + janus_mutex_unlock(&session->trunk_cond_mutex); + json_decref(event); + } + } else { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(event); + } } break; } case nua_r_notify: { - JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", username, nua_event_name(event), status, phrase ? phrase : "??"); /* We got a response to a NOTIFY we sent, but we really don't care */ break; } @@ -7608,77 +7993,147 @@ gpointer janus_sip_sofia_thread(gpointer user_data) { g_thread_unref(g_thread_self()); return NULL; } - JANUS_LOG(LOG_VERB, "Joining sofia loop thread (%s)...\n", session->account.username); - session->stack = g_malloc0(sizeof(ssip_t)); - su_home_init(session->stack->s_home); - session->stack->session = session; - session->stack->s_nua = NULL; - session->stack->s_nh_r = NULL; - session->stack->s_nh_i = NULL; - session->stack->s_nh_m = NULL; - session->stack->s_root = su_root_create(session->stack); - session->stack->subscriptions = NULL; - janus_mutex_init(&session->stack->smutex); - JANUS_LOG(LOG_VERB, "Setting up sofia stack (sip:%s@%s)\n", session->account.username, local_ip); + gboolean is_trunk = (session->trunk && sip_trunk && sip_trunk->session == session); + gboolean is_trunk_user = (session->trunk && sip_trunk && sip_trunk->session != session); + JANUS_LOG(LOG_VERB, "Joining sofia loop thread (%s, %s)...\n", session->account.username, + (is_trunk ? "trunk" : (is_trunk_user ? "trunk-user" : "user"))); char sip_url[128]; char sips_url[128]; - char *ipv6; - ipv6 = strstr(local_ip, ":"); - if(session->account.force_tcp) - g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=tcp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); - else - g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=udp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); - g_snprintf(sips_url, sizeof(sips_url), "sips:%s%s%s:*;transport=tls", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); - char outbound_options[256] = "use-rport no-validate"; - if(keepalive_interval > 0) - janus_strlcat(outbound_options, " options-keepalive", sizeof(outbound_options)); - if(!behind_nat) - janus_strlcat(outbound_options, " no-natify", sizeof(outbound_options)); - session->stack->s_nua = nua_create(session->stack->s_root, - janus_sip_sofia_callback, - session, - SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, REFER, MESSAGE, INFO, NOTIFY"), - NUTAG_M_USERNAME(session->account.username), - NUTAG_URL(sip_url), - TAG_IF(session->account.sips, NUTAG_SIPS_URL(sips_url)), - TAG_IF(session->account.sips && sips_certs_dir, NUTAG_CERTIFICATE_DIR(sips_certs_dir)), - SIPTAG_USER_AGENT_STR(session->account.user_agent ? session->account.user_agent : user_agent), - NUTAG_KEEPALIVE(keepalive_interval * 1000), /* Sofia expects it in milliseconds */ - NUTAG_OUTBOUND(outbound_options), - NUTAG_APPL_METHOD("REFER"), /* We'll respond to incoming REFER messages ourselves */ - SIPTAG_SUPPORTED_STR("replaces"), /* Advertise that we support the Replaces header */ - SIPTAG_SUPPORTED(NULL), - NTATAG_CANCEL_2543(session->account.rfc2543_cancel), - NTATAG_SIP_T1X64(sip_timer_t1x64), - TAG_NULL()); - if(query_contact_header) - nua_get_params(session->stack->s_nua, SIPTAG_FROM_STR(""), TAG_END()); - su_root_run(session->stack->s_root); + if(is_trunk_user) { + /* For trunk users, don't do anything */ + sip_url[0] = '\0'; + sips_url[0] = '\0'; + } else if(!is_trunk) { + /* For users, we use the local IP and bind to a random port */ + char *ipv6 = strstr(local_ip, ":"); + if(session->account.force_tcp) + g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=tcp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); + else + g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=udp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); + g_snprintf(sips_url, sizeof(sips_url), "sips:%s%s%s:*;transport=tls", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : ""); + } else { + /* For trunks, we bind to exactly what we were asked to bind to */ + if(session->account.force_tcp) + g_snprintf(sip_url, sizeof(sip_url), "sip:%s;transport=tcp", sip_trunk->local); + else + g_snprintf(sip_url, sizeof(sip_url), "sip:%s;transport=udp", sip_trunk->local); + g_snprintf(sips_url, sizeof(sips_url), "sip:%s;transport=tls", sip_trunk->local); + } + + /* Check if we need to actually create a SIP stack */ + if(!is_trunk_user) { + /* Regular users and the session we use for trunks do need a stack */ + JANUS_LOG(LOG_VERB, "Setting up sofia stack (%s)\n", sip_url); + session->stack = g_malloc0(sizeof(ssip_t)); + session->stack->session = session; + janus_mutex_init(&session->stack->smutex); + su_home_init(session->stack->s_home); + session->stack->s_root = su_root_create(session->stack); + char outbound_options[256] = "use-rport no-validate"; + if(keepalive_interval > 0) + janus_strlcat(outbound_options, " options-keepalive", sizeof(outbound_options)); + if(!behind_nat) + janus_strlcat(outbound_options, " no-natify", sizeof(outbound_options)); + session->stack->s_nua = nua_create(session->stack->s_root, + janus_sip_sofia_callback, + session, + SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, REFER, MESSAGE, INFO, NOTIFY"), + NUTAG_M_USERNAME(session->account.username), + NUTAG_URL(sip_url), + TAG_IF(session->account.sips, NUTAG_SIPS_URL(sips_url)), + TAG_IF(session->account.sips && sips_certs_dir, NUTAG_CERTIFICATE_DIR(sips_certs_dir)), + SIPTAG_USER_AGENT_STR(session->account.user_agent ? session->account.user_agent : user_agent), + NUTAG_KEEPALIVE(keepalive_interval * 1000), /* Sofia expects it in milliseconds */ + NUTAG_OUTBOUND(outbound_options), + NUTAG_APPL_METHOD("REFER"), /* We'll respond to incoming REFER messages ourselves */ + SIPTAG_SUPPORTED_STR("replaces"), /* Advertise that we support the Replaces header */ + SIPTAG_SUPPORTED(NULL), + NTATAG_CANCEL_2543(session->account.rfc2543_cancel), + NTATAG_SIP_T1X64(sip_timer_t1x64), + TAG_NULL()); + if(query_contact_header) + nua_get_params(session->stack->s_nua, SIPTAG_FROM_STR(""), TAG_END()); + su_root_run(session->stack->s_root); + } else { + /* Trunk users will not create a SIP stack, but need this thread + * to send Janus API messages without keeping the Sofia loop busy */ + JANUS_LOG(LOG_VERB, "Setting up trunk user\n"); + session->stack = sip_trunk->session->stack; + session->account.contact_header = g_strdup(sip_trunk->session->account.contact_header); + session->trunk_events = g_queue_new(); + janus_mutex_init(&session->trunk_cond_mutex); + janus_sip_trunk_event *event = NULL; + gboolean go_on = TRUE; + while(go_on) { + janus_mutex_lock(&session->trunk_cond_mutex); + event = g_queue_peek_head(session->trunk_events); + if(event == NULL) + janus_condition_wait(&session->trunk_cond, &session->trunk_cond_mutex); + while((event = g_queue_pop_head(session->trunk_events)) != NULL) { + if(event == &janus_sip_trunk_event_quit) { + go_on = FALSE; + break; + } + if(event->msg) { + int ret = gateway->push_event(session->handle, &janus_sip_plugin, + session->transaction, event->msg, event->jsep); + JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); + } + janus_sip_trunk_event_destroy(event); + } + janus_mutex_unlock(&session->trunk_cond_mutex); + } + } /* When we get here, we're done */ janus_mutex_lock(&session->stack->smutex); nua_t *s_nua = session->stack->s_nua; - session->stack->s_nua = NULL; + if(!is_trunk_user) + session->stack->s_nua = NULL; janus_mutex_unlock(&session->stack->smutex); - if(session->stack->s_nh_r != NULL) { - nua_handle_destroy(session->stack->s_nh_r); - session->stack->s_nh_r = NULL; - } - if(session->stack->s_nh_i != NULL) { - nua_handle_destroy(session->stack->s_nh_i); - session->stack->s_nh_i = NULL; - } - if(session->stack->s_nh_m != NULL) { - nua_handle_destroy(session->stack->s_nh_m); - session->stack->s_nh_m = NULL; + if(session->account.s_nh_r != NULL) { + if(session->trunk) { + janus_mutex_lock(&sip_trunk->mutex); + g_hash_table_remove(sip_trunk->sessions_bynh, session->account.s_nh_r); + janus_mutex_unlock(&sip_trunk->mutex); + } + nua_handle_destroy(session->account.s_nh_r); + session->account.s_nh_r = NULL; + } + if(session->account.s_nh_i != NULL) { + if(session->trunk) { + janus_mutex_lock(&sip_trunk->mutex); + g_hash_table_remove(sip_trunk->sessions_bynh, session->account.s_nh_i); + janus_mutex_unlock(&sip_trunk->mutex); + } + nua_handle_destroy(session->account.s_nh_i); + session->account.s_nh_i = NULL; + } + if(session->account.s_nh_m != NULL) { + if(session->trunk) { + janus_mutex_lock(&sip_trunk->mutex); + g_hash_table_remove(sip_trunk->sessions_bynh, session->account.s_nh_m); + janus_mutex_unlock(&sip_trunk->mutex); + } + nua_handle_destroy(session->account.s_nh_m); + session->account.s_nh_m = NULL; } janus_mutex_lock(&session->stack->smutex); - if(session->stack->subscriptions != NULL) - g_hash_table_unref(session->stack->subscriptions); - session->stack->subscriptions = NULL; + if(session->account.subscriptions != NULL) + g_hash_table_unref(session->account.subscriptions); + session->account.subscriptions = NULL; janus_mutex_unlock(&session->stack->smutex); - nua_destroy(s_nua); - su_root_destroy(session->stack->s_root); - session->stack->s_root = NULL; + /* Only destroy the NUA and the root if this isn't a trunk user */ + if(!is_trunk_user) { + if(s_nua != NULL) + nua_destroy(s_nua); + su_root_destroy(session->stack->s_root); + session->stack->s_root = NULL; + } else { + janus_mutex_lock(&session->mutex); + g_queue_free_full(session->trunk_events, (GDestroyNotify)janus_sip_trunk_event_destroy); + session->trunk_events = NULL; + janus_mutex_unlock(&session->mutex); + } janus_refcount_decrease(&session->ref); JANUS_LOG(LOG_VERB, "Leaving sofia loop thread...\n"); g_thread_unref(g_thread_self());