diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 2358a72813..0610c95e72 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -1259,6 +1259,30 @@ int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint, void *token, void (*callback)(void *token, pjsip_event *e)); +/*! + * \brief General purpose method for sending an Out-Of-Dialog SIP request + * + * This is a companion function for \ref ast_sip_create_request. The request + * created there can be passed to this function, though any request may be + * passed in. + * + * This will automatically set up handling outbound authentication challenges if + * they arrive. + * + * \param tdata The request to send + * \param endpoint Optional. If specified, the out-of-dialog request is sent to the endpoint. + * \param timeout. If non-zero, after the timeout the transaction will be terminated + * and the callback will be called with the PJSIP_EVENT_TIMER type. + * \param token Data to be passed to the callback upon receipt of out-of-dialog response. + * \param callback Callback to be called upon receipt of out-of-dialog response. + * + * \retval 0 Success + * \retval -1 Failure (out-of-dialog callback will not be called.) + */ +int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, + struct ast_sip_endpoint *endpoint, int timeout, void *token, + void (*callback)(void *token, pjsip_event *e)); + /*! * \brief General purpose method for creating a SIP response * diff --git a/res/res_pjsip.c b/res/res_pjsip.c index fcd8516b65..108c5b32dd 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -21,6 +21,8 @@ #include /* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */ #include +#include +#include #include #include "asterisk/res_pjsip.h" @@ -2815,6 +2817,128 @@ static pj_bool_t does_method_match(const pj_str_t *message_method, const char *s /*! Maximum number of challenges before assuming that we are in a loop */ #define MAX_RX_CHALLENGES 10 +#define TIMER_INACTIVE 0 +#define TIMEOUT_TIMER2 5 + +struct tsx_data { + void *token; + void (*cb)(void*, pjsip_event*); + pjsip_transaction *tsx; + pj_timer_entry *timeout_timer; +}; + +static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event); + +pjsip_module send_tsx_module = { + .name = { "send_tsx_module", 23 }, + .id = -1, + .priority = PJSIP_MOD_PRIORITY_APPLICATION, + .on_tsx_state = &send_tsx_on_tsx_state, +}; + +/*! \brief This is the pjsip_tsx_send_msg callback */ +static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + struct tsx_data *tsx_data; + + if (event->type != PJSIP_EVENT_TSX_STATE) { + return; + } + + tsx_data = (struct tsx_data*) tsx->mod_data[send_tsx_module.id]; + if (tsx_data == NULL) { + return; + } + + if (tsx->status_code < 200) { + return; + } + + if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) { + ast_debug(1, "PJSIP tsx timer expired\n"); + } + + if (tsx_data->timeout_timer && tsx_data->timeout_timer->id != TIMER_INACTIVE) { + pj_mutex_lock(tsx->mutex_b); + pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt), + tsx_data->timeout_timer, TIMER_INACTIVE); + pj_mutex_unlock(tsx->mutex_b); + } + + /* Call the callback, if any, and prevent the callback from being called again + * by clearing the transaction's module_data. + */ + tsx->mod_data[send_tsx_module.id] = NULL; + + if (tsx_data->cb) { + (*tsx_data->cb)(tsx_data->token, event); + } +} + +static void tsx_timer_callback(pj_timer_heap_t *theap, pj_timer_entry *entry) +{ + struct tsx_data *tsx_data = entry->user_data; + + entry->id = TIMER_INACTIVE; + ast_debug(1, "Internal tsx timer expired\n"); + pjsip_tsx_terminate(tsx_data->tsx, PJSIP_SC_TSX_TIMEOUT); +} + +static pj_status_t endpt_send_transaction(pjsip_endpoint *endpt, + pjsip_tx_data *tdata, int timeout, void *token, + pjsip_endpt_send_callback cb) +{ + pjsip_transaction *tsx; + struct tsx_data *tsx_data; + pj_status_t status; + pjsip_event event; + + ast_assert(endpt && tdata); + + status = pjsip_tsx_create_uac(&send_tsx_module, tdata, &tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + ast_log(LOG_ERROR, "Unable to create pjsip uac\n"); + return status; + } + + tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data); + tsx_data->token = token; + tsx_data->cb = cb; + tsx_data->tsx = tsx; + if (timeout > 0) { + tsx_data->timeout_timer = PJ_POOL_ALLOC_T(tsx->pool, pj_timer_entry); + } else { + tsx_data->timeout_timer = NULL; + } + tsx->mod_data[send_tsx_module.id] = tsx_data; + + PJSIP_EVENT_INIT_TX_MSG(event, tdata); + pjsip_tx_data_set_transport(tdata, &tsx->tp_sel); + + if (timeout > 0) { + pj_time_val timeout_timer_val = { timeout / 1000, timeout % 1000 }; + + pj_timer_entry_init(tsx_data->timeout_timer, TIMEOUT_TIMER2, + tsx_data, &tsx_timer_callback); + pj_mutex_lock(tsx->mutex_b); + pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt), + tsx_data->timeout_timer, TIMER_INACTIVE); + pj_timer_heap_schedule(pjsip_endpt_get_timer_heap(tsx->endpt), + tsx_data->timeout_timer, &timeout_timer_val); + tsx_data->timeout_timer->id = TIMEOUT_TIMER2; + pj_mutex_unlock(tsx->mutex_b); + } + + status = (*tsx->state_handler)(tsx, &event); + pjsip_tx_data_dec_ref(tdata); + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to send message\n"); + return status; + } + + return status; +} /*! \brief Structure to hold information about an outbound request */ struct send_request_data { @@ -2874,7 +2998,7 @@ static void endpt_send_request_wrapper(void *token, pjsip_event *e) } static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, - pjsip_tx_data *tdata, pj_int32_t timeout, void *token, pjsip_endpt_send_callback cb) + pjsip_tx_data *tdata, int timeout, void *token, pjsip_endpt_send_callback cb) { struct send_request_wrapper *req_wrapper; pj_status_t ret_val; @@ -2890,7 +3014,7 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, req_wrapper->callback = cb; ao2_ref(req_wrapper, +1); - ret_val = pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, timeout, + ret_val = endpt_send_transaction(ast_sip_get_pjsip_endpoint(), tdata, timeout, req_wrapper, endpt_send_request_wrapper); if (ret_val != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -2930,6 +3054,10 @@ static void send_request_cb(void *token, pjsip_event *e) int res; switch(e->body.tsx_state.type) { + case PJSIP_EVENT_USER: + /* Map USER (transaction cancelled by timeout) to TIMER */ + e->body.tsx_state.type = PJSIP_EVENT_TIMER; + break; case PJSIP_EVENT_TRANSPORT_ERROR: case PJSIP_EVENT_TIMER: break; @@ -2980,8 +3108,9 @@ static void send_request_cb(void *token, pjsip_event *e) ao2_ref(req_data, -1); } -static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint, - void *token, void (*callback)(void *token, pjsip_event *e)) +int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, + struct ast_sip_endpoint *endpoint, int timeout, void *token, + void (*callback)(void *token, pjsip_event *e)) { struct ast_sip_supplement *supplement; struct send_request_data *req_data; @@ -3007,7 +3136,7 @@ static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpo ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL); ao2_cleanup(contact); - if (endpt_send_request(endpoint, tdata, -1, req_data, send_request_cb) + if (endpt_send_request(endpoint, tdata, timeout, req_data, send_request_cb) != PJ_SUCCESS) { ao2_cleanup(req_data); return -1; @@ -3025,7 +3154,7 @@ int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg, if (dlg) { return send_in_dialog_request(tdata, dlg); } else { - return send_out_of_dialog_request(tdata, endpoint, token, callback); + return ast_sip_send_out_of_dialog_request(tdata, endpoint, -1, token, callback); } } @@ -3543,8 +3672,25 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; } + if (internal_sip_register_service(&send_tsx_module)) { + ast_log(LOG_ERROR, "Failed to initialize send request module. Aborting load\n"); + internal_sip_unregister_service(&supplement_module); + ast_sip_destroy_distributor(); + ast_res_pjsip_destroy_configuration(); + ast_sip_destroy_global_headers(); + stop_monitor_thread(); + ast_sip_destroy_system(); + pj_pool_release(memory_pool); + memory_pool = NULL; + pjsip_endpt_destroy(ast_pjsip_endpoint); + ast_pjsip_endpoint = NULL; + pj_caching_pool_destroy(&caching_pool); + return AST_MODULE_LOAD_DECLINE; + } + if (internal_sip_initialize_outbound_authentication()) { ast_log(LOG_ERROR, "Failed to initialize outbound authentication. Aborting load\n"); + internal_sip_unregister_service(&send_tsx_module); internal_sip_unregister_service(&supplement_module); ast_sip_destroy_distributor(); ast_res_pjsip_destroy_configuration(); @@ -3588,6 +3734,7 @@ static int unload_pjsip(void *data) ast_res_pjsip_destroy_configuration(); ast_sip_destroy_system(); ast_sip_destroy_global_headers(); + internal_sip_unregister_service(&send_tsx_module); internal_sip_unregister_service(&supplement_module); if (monitor_thread) { stop_monitor_thread();