diff options
-rw-r--r-- | CHANGES | 19 | ||||
-rw-r--r-- | channels/chan_sip.c | 11 | ||||
-rw-r--r-- | configs/samples/pjsip.conf.sample | 8 | ||||
-rw-r--r-- | contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py | 22 | ||||
-rw-r--r-- | funcs/func_holdintercept.c | 236 | ||||
-rw-r--r-- | include/asterisk/res_pjsip.h | 5 | ||||
-rw-r--r-- | include/asterisk/res_pjsip_pubsub.h | 9 | ||||
-rw-r--r-- | main/format_cap.c | 29 | ||||
-rw-r--r-- | main/manager.c | 12 | ||||
-rw-r--r-- | main/strings.c | 91 | ||||
-rw-r--r-- | main/xmldoc.c | 6 | ||||
-rw-r--r-- | res/res_pjsip.c | 18 | ||||
-rw-r--r-- | res/res_pjsip/location.c | 32 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_configuration.c | 30 | ||||
-rw-r--r-- | res/res_pjsip_exten_state.c | 4 | ||||
-rw-r--r-- | res/res_pjsip_mwi.c | 6 | ||||
-rw-r--r-- | res/res_pjsip_outbound_registration.c | 29 | ||||
-rw-r--r-- | res/res_pjsip_pubsub.c | 339 | ||||
-rw-r--r-- | res/res_pjsip_pubsub.exports.in | 1 | ||||
-rw-r--r-- | rest-api-templates/api.wiki.mustache | 2 | ||||
-rw-r--r-- | rest-api-templates/asterisk_processor.py | 2 |
21 files changed, 701 insertions, 210 deletions
@@ -9,6 +9,25 @@ ============================================================================== ------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------ +------------------------------------------------------------------------------ + +Dialplan Functions +------------------ + * The HOLD_INTERCEPT dialplan function now actually exists in the source tree. + While support for the events was added in Asterisk 13.4.0, the function + accidentally never made it in. That function is now present, and will cause + the 'hold' raised by a channel to be intercepted and converted into an + event instead. + +res_pjsip_outbound_registration +------------------------------- +* A new 'fatal_retry_interval' option has been added to outbound registration. + When set (default is zero), and upon receiving a failure response to an + outbound registration, registration is retried at the given interval up to + 'max_retries'. + +------------------------------------------------------------------------------ --- Functionality changes from Asterisk 13.5.0 to Asterisk 13.6.0 ------------ ------------------------------------------------------------------------------ diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 051bb2bc4..8a7ca5454 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -11095,7 +11095,7 @@ static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_ if (framing && p->autoframing) { ast_debug(1, "Setting framing to %ld\n", framing); - ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), framing); + ast_format_cap_set_framing(p->caps, framing); } found = TRUE; } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { @@ -13384,6 +13384,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int ast_str_append(&a_audio, 0, "a=maxptime:%d\r\n", max_audio_packet_size); } + if (!ast_test_flag(&p->flags[0], SIP_OUTGOING)) { + ast_debug(1, "Setting framing on incoming call: %u\n", min_audio_packet_size); + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), min_audio_packet_size); + } + if (!doing_directmedia) { if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { add_ice_to_sdp(p->rtp, &a_audio); @@ -13676,10 +13681,6 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const add_cc_call_info_to_response(p, &resp); } if (p->rtp) { - if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { - ast_debug(1, "Setting framing from config on incoming call\n"); - ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(p->caps)); - } ast_rtp_instance_activate(p->rtp); try_suggested_sip_codec(p); if (p->t38.state == T38_ENABLED) { diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index fc7094756..859635c98 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -923,6 +923,14 @@ ; registration is unsuccessful (default: "60") ;forbidden_retry_interval=0 ; Interval used when receiving a 403 Forbidden ; response (default: "0") +;fatal_retry_interval=0 ; Interval used when receiving a fatal response. + ; (default: "0") A fatal response is any permanent + ; failure (non-temporary 4xx, 5xx, 6xx) response + ; received from the registrar. NOTE - if also set + ; the 'forbidden_retry_interval' takes precedence + ; over this one when a 403 is received. Also, if + ; 'auth_rejection_permanent' equals 'yes' a 401 and + ; 407 become subject to this retry interval. ;server_uri= ; SIP URI of the server to register against (default: "") ;transport= ; Transport used for outbound authentication (default: "") ;type= ; Must be of type registration (default: "") diff --git a/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py b/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py new file mode 100644 index 000000000..8c499aee8 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py @@ -0,0 +1,22 @@ +"""add fatal_response_interval + +Revision ID: 28ce1e718f05 +Revises: 154177371065 +Create Date: 2015-10-20 17:57:45.560585 + +""" + +# revision identifiers, used by Alembic. +revision = '28ce1e718f05' +down_revision = '154177371065' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('ps_registrations', sa.Column('fatal_retry_interval', sa.Integer)) + + +def downgrade(): + op.drop_column('ps_registrations', 'fatal_retry_interval') diff --git a/funcs/func_holdintercept.c b/funcs/func_holdintercept.c new file mode 100644 index 000000000..56d9a9e83 --- /dev/null +++ b/funcs/func_holdintercept.c @@ -0,0 +1,236 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Digium, Inc. + * + * Matt Jordan <mjordan@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Function that intercepts HOLD frames from channels and raises events + * + * \author Matt Jordan <mjordan@digium.com> + * + * \ingroup functions + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/frame.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" + +/*** DOCUMENTATION + <function name="HOLD_INTERCEPT" language="en_US"> + <synopsis> + Intercepts hold frames on a channel and raises an event instead of passing the frame on + </synopsis> + <syntax> + <parameter name="action" required="true"> + <optionlist> + <option name="remove"> + <para>W/O. Removes the hold interception function.</para> + </option> + <option name="set"> + <para>W/O. Enable hold interception on the channel. When + enabled, the channel will intercept any hold action that + is signalled from the device, and instead simply raise an + event (AMI/ARI) indicating that the channel wanted to put other + parties on hold.</para> + </option> + </optionlist> + </parameter> + </syntax> + </function> +***/ + +/*! \brief Private data structure used with the function's datastore */ +struct hold_intercept_data { + int framehook_id; +}; + +/*! \brief The channel datastore the function uses to store state */ +static const struct ast_datastore_info hold_intercept_datastore = { + .type = "hold_intercept", +}; + +/*! \internal \brief Disable hold interception on the channel */ +static int remove_hold_intercept(struct ast_channel *chan) +{ + struct ast_datastore *datastore = NULL; + struct hold_intercept_data *data; + SCOPED_CHANNELLOCK(chan_lock, chan); + + datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL); + if (!datastore) { + ast_log(AST_LOG_WARNING, "Cannot remove HOLD_INTERCEPT from %s: HOLD_INTERCEPT not currently enabled\n", + ast_channel_name(chan)); + return -1; + } + data = datastore->data; + + if (ast_framehook_detach(chan, data->framehook_id)) { + ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT framehook from channel %s\n", + ast_channel_name(chan)); + return -1; + } + + if (ast_channel_datastore_remove(chan, datastore)) { + ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT datastore from channel %s\n", + ast_channel_name(chan)); + return -1; + } + ast_datastore_free(datastore); + + return 0; +} + +/*! \brief Frame hook that is called to intercept hold/unhold */ +static struct ast_frame *hold_intercept_framehook(struct ast_channel *chan, + struct ast_frame *f, enum ast_framehook_event event, void *data) +{ + int frame_type; + + if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) { + return f; + } + + if (f->frametype != AST_FRAME_CONTROL) { + return f; + } + + frame_type = f->subclass.integer; + if (frame_type != AST_CONTROL_HOLD && frame_type != AST_CONTROL_UNHOLD) { + return f; + } + + /* Munch munch */ + ast_frfree(f); + f = &ast_null_frame; + + ast_channel_publish_cached_blob(chan, + frame_type == AST_CONTROL_HOLD ? ast_channel_hold_type() : ast_channel_unhold_type(), + NULL); + + return f; +} + +/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */ +static int hold_intercept_framehook_consume(void *data, enum ast_frame_type type) +{ + return (type == AST_FRAME_CONTROL ? 1 : 0); +} + +/*! \internal \brief Enable hold interception on the channel */ +static int set_hold_intercept(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + struct hold_intercept_data *data; + static struct ast_framehook_interface hold_framehook_interface = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = hold_intercept_framehook, + .consume_cb = hold_intercept_framehook_consume, + .disable_inheritance = 1, + }; + SCOPED_CHANNELLOCK(chan_lock, chan); + + datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL); + if (datastore) { + ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT already set on '%s'\n", + ast_channel_name(chan)); + return 0; + } + + datastore = ast_datastore_alloc(&hold_intercept_datastore, NULL); + if (!datastore) { + return -1; + } + + data = ast_calloc(1, sizeof(*data)); + if (!data) { + ast_datastore_free(datastore); + return -1; + } + + data->framehook_id = ast_framehook_attach(chan, &hold_framehook_interface); + if (data->framehook_id < 0) { + ast_log(AST_LOG_WARNING, "Failed to attach HOLD_INTERCEPT framehook to '%s'\n", + ast_channel_name(chan)); + ast_datastore_free(datastore); + ast_free(data); + return -1; + } + datastore->data = data; + + ast_channel_datastore_add(chan, datastore); + + return 0; +} + +/*! \internal \brief HOLD_INTERCEPT write function callback */ +static int hold_intercept_fn_write(struct ast_channel *chan, const char *function, + char *data, const char *value) +{ + int res; + + if (!chan) { + return -1; + } + + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT requires an argument\n"); + return -1; + } + + if (!strcasecmp(data, "set")) { + res = set_hold_intercept(chan); + } else if (!strcasecmp(data, "remove")) { + res = remove_hold_intercept(chan); + } else { + ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT: unknown option %s\n", data); + res = -1; + } + + return res; +} + +/*! \brief Definition of the HOLD_INTERCEPT function */ +static struct ast_custom_function hold_intercept_function = { + .name = "HOLD_INTERCEPT", + .write = hold_intercept_fn_write, +}; + +/*! \internal \brief Unload the module */ +static int unload_module(void) +{ + return ast_custom_function_unregister(&hold_intercept_function); +} + +/*! \internal \brief Load the module */ +static int load_module(void) +{ + return ast_custom_function_register(&hold_intercept_function) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hold interception dialplan function"); diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index a36935dd3..459082901 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -1190,6 +1190,11 @@ int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void * cause a deadlock. If you are in a SIP servant thread, just call your function * in-line. * + * \warning \b Never hold locks that may be acquired by a SIP servant thread when + * calling this function. Doing so may cause a deadlock if all SIP servant threads + * are blocked waiting to acquire the lock while the thread holding the lock is + * waiting for a free SIP servant thread. + * * \param serializer The SIP serializer to which the task belongs. May be NULL. * \param sip_task The task to execute * \param task_data The parameter to pass to the task when it executes diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h index afa0d6930..c9b66dce3 100644 --- a/include/asterisk/res_pjsip_pubsub.h +++ b/include/asterisk/res_pjsip_pubsub.h @@ -684,6 +684,15 @@ const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub) */ const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub); +/*! + * \since 13.6.0 + * \brief Alert the pubsub core that the subscription is ready for destruction + * + * \param sub The subscription that is complete + * \return Nothing + */ +void ast_sip_subscription_destroy(struct ast_sip_subscription *sub); + /*! \brief Determines whether the res_pjsip_pubsub module is loaded */ #define CHECK_PJSIP_PUBSUB_MODULE_LOADED() \ do { \ diff --git a/main/format_cap.c b/main/format_cap.c index b344f846e..1725e3862 100644 --- a/main/format_cap.c +++ b/main/format_cap.c @@ -93,14 +93,27 @@ static void format_cap_destroy(void *obj) AST_VECTOR_FREE(&cap->preference_order); } -static inline void format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags) +/* + * \brief Initialize values on an ast_format_cap + * + * \param cap ast_format_cap to initialize + * \param flags Unused. + * \retval 0 Success + * \retval -1 Failure + */ +static inline int format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags) { - AST_VECTOR_INIT(&cap->formats, 0); + if (AST_VECTOR_INIT(&cap->formats, 0)) { + return -1; + } /* TODO: Look at common usage of this and determine a good starting point */ - AST_VECTOR_INIT(&cap->preference_order, 5); + if (AST_VECTOR_INIT(&cap->preference_order, 5)) { + return -1; + } cap->framing = UINT_MAX; + return 0; } struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags) @@ -112,7 +125,10 @@ struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags) return NULL; } - format_cap_init(cap, flags); + if (format_cap_init(cap, flags)) { + ao2_ref(cap, -1); + return NULL; + } return cap; } @@ -126,7 +142,10 @@ struct ast_format_cap *__ast_format_cap_alloc_debug(enum ast_format_cap_flags fl return NULL; } - format_cap_init(cap, flags); + if (format_cap_init(cap, flags)) { + ao2_ref(cap, -1); + return NULL; + } return cap; } diff --git a/main/manager.c b/main/manager.c index 75b1d4d48..8295303c7 100644 --- a/main/manager.c +++ b/main/manager.c @@ -2811,6 +2811,7 @@ AST_THREADSTORAGE(userevent_buf); */ void astman_append(struct mansession *s, const char *fmt, ...) { + int res; va_list ap; struct ast_str *buf; @@ -2819,8 +2820,11 @@ void astman_append(struct mansession *s, const char *fmt, ...) } va_start(ap, fmt); - ast_str_set_va(&buf, 0, fmt, ap); + res = ast_str_set_va(&buf, 0, fmt, ap); va_end(ap); + if (res == AST_DYNSTR_BUILD_FAILED) { + return; + } if (s->f != NULL || s->session->f != NULL) { send_string(s, ast_str_buffer(buf)); @@ -2880,6 +2884,7 @@ void astman_send_error(struct mansession *s, const struct message *m, char *erro void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...) { + int res; va_list ap; struct ast_str *buf; char *msg; @@ -2889,8 +2894,11 @@ void astman_send_error_va(struct mansession *s, const struct message *m, const c } va_start(ap, fmt); - ast_str_set_va(&buf, 0, fmt, ap); + res = ast_str_set_va(&buf, 0, fmt, ap); va_end(ap); + if (res == AST_DYNSTR_BUILD_FAILED) { + return; + } /* astman_append will use the same underlying buffer, so copy the message out * before sending the response */ diff --git a/main/strings.c b/main/strings.c index da63cdfc0..53d50954f 100644 --- a/main/strings.c +++ b/main/strings.c @@ -60,55 +60,78 @@ int __ast_str_helper(struct ast_str **buf, ssize_t max_len, int append, const char *fmt, va_list ap) #endif { - int res, need; + int res; + int added; + int need; int offset = (append && (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_USED : 0; va_list aq; + if (max_len < 0) { + max_len = (*buf)->__AST_STR_LEN; /* don't exceed the allocated space */ + } + do { - if (max_len < 0) { - max_len = (*buf)->__AST_STR_LEN; /* don't exceed the allocated space */ - } - /* - * Ask vsnprintf how much space we need. Remember that vsnprintf - * does not count the final <code>'\\0'</code> so we must add 1. - */ va_copy(aq, ap); res = vsnprintf((*buf)->__AST_STR_STR + offset, (*buf)->__AST_STR_LEN - offset, fmt, aq); + va_end(aq); + + if (res < 0) { + /* + * vsnprintf write to string failed. + * I don't think this is possible with a memory buffer. + */ + res = AST_DYNSTR_BUILD_FAILED; + added = 0; + break; + } - need = res + offset + 1; /* - * If there is not enough space and we are below the max length, - * reallocate the buffer and return a message telling to retry. + * vsnprintf returns how much space we used or would need. + * Remember that vsnprintf does not count the nil terminator + * so we must add 1. */ - if (need > (*buf)->__AST_STR_LEN && (max_len == 0 || (*buf)->__AST_STR_LEN < max_len) ) { - int len = (int)(*buf)->__AST_STR_LEN; - if (max_len && max_len < need) { /* truncate as needed */ - need = max_len; - } else if (max_len == 0) { /* if unbounded, give more room for next time */ - need += 16 + need / 4; - } - if ( + added = res; + need = offset + added + 1; + if (need <= (*buf)->__AST_STR_LEN + || (max_len && max_len <= (*buf)->__AST_STR_LEN)) { + /* + * There was enough room for the string or we are not + * allowed to try growing the string buffer. + */ + break; + } + + /* Reallocate the buffer and try again. */ + if (max_len == 0) { + /* unbounded, give more room for next time */ + need += 16 + need / 4; + } else if (max_len < need) { + /* truncate as needed */ + need = max_len; + } + + if ( #if (defined(MALLOC_DEBUG) && !defined(STANDALONE)) - _ast_str_make_space(buf, need, file, lineno, function) + _ast_str_make_space(buf, need, file, lineno, function) #else - ast_str_make_space(buf, need) + ast_str_make_space(buf, need) #endif - ) { - ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n", len, need); - va_end(aq); - return AST_DYNSTR_BUILD_FAILED; - } - (*buf)->__AST_STR_STR[offset] = '\0'; /* Truncate the partial write. */ + ) { + ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n", + (int) (*buf)->__AST_STR_LEN, need); - /* Restart va_copy before calling vsnprintf() again. */ - va_end(aq); - continue; + res = AST_DYNSTR_BUILD_FAILED; + break; } - va_end(aq); - break; } while (1); - /* update space used, keep in mind the truncation */ - (*buf)->__AST_STR_USED = (res + offset > (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_LEN - 1: res + offset; + + /* Update space used, keep in mind truncation may be necessary. */ + (*buf)->__AST_STR_USED = ((*buf)->__AST_STR_LEN <= offset + added) + ? (*buf)->__AST_STR_LEN - 1 + : offset + added; + + /* Ensure that the string is terminated. */ + (*buf)->__AST_STR_STR[(*buf)->__AST_STR_USED] = '\0'; return res; } diff --git a/main/xmldoc.c b/main/xmldoc.c index fcf1b2cac..1a04e8168 100644 --- a/main/xmldoc.c +++ b/main/xmldoc.c @@ -2646,14 +2646,18 @@ struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_q struct documentation_tree *doctree; RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free); va_list ap; + int res; if (!xpath_str) { return NULL; } va_start(ap, fmt); - ast_str_set_va(&xpath_str, 0, fmt, ap); + res = ast_str_set_va(&xpath_str, 0, fmt, ap); va_end(ap); + if (res == AST_DYNSTR_BUILD_FAILED) { + return NULL; + } AST_RWLIST_RDLOCK(&xmldoc_tree); AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 9e5e404b6..d2b393fcc 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -2563,6 +2563,8 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint, pj_strdup2_with_null(dlg->pool, &tmp, outbound_proxy); if (!(route = pjsip_parse_hdr(dlg->pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL))) { + ast_log(LOG_ERROR, "Could not create dialog to endpoint '%s' as outbound proxy URI '%s' is not valid\n", + ast_sorcery_object_get_id(endpoint), outbound_proxy); dlg->sess_count--; pjsip_dlg_terminate(dlg); return NULL; @@ -2756,6 +2758,7 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s pj_str_t from; pj_pool_t *pool; pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, }; + pjsip_uri *sip_uri; if (ast_strlen_zero(uri)) { if (!endpoint && (!contact || ast_strlen_zero(contact->uri))) { @@ -2792,6 +2795,16 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s return -1; } + sip_uri = pjsip_parse_uri(pool, remote_uri.ptr, remote_uri.slen, 0); + if (!sip_uri || (!PJSIP_URI_SCHEME_IS_SIP(sip_uri) && !PJSIP_URI_SCHEME_IS_SIPS(sip_uri))) { + ast_log(LOG_ERROR, "Unable to create outbound %.*s request to endpoint %s as URI '%s' is not valid\n", + (int) pj_strlen(&method->name), pj_strbuf(&method->name), + endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>", + pj_strbuf(&remote_uri)); + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + return -1; + } + if (sip_dialog_create_from(pool, &from, endpoint ? endpoint->fromuser : NULL, endpoint ? endpoint->fromdomain : NULL, &remote_uri, &selector)) { ast_log(LOG_ERROR, "Unable to create From header for %.*s request to endpoint %s\n", @@ -2816,8 +2829,9 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s /* If an outbound proxy is specified on the endpoint apply it to this request */ if (endpoint && !ast_strlen_zero(endpoint->outbound_proxy) && ast_sip_set_outbound_proxy((*tdata), endpoint->outbound_proxy)) { - ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s\n", - (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint)); + ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s as outbound proxy URI '%s' is not valid\n", + (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint), + endpoint->outbound_proxy); pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); return -1; } diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index 9625f04ef..331d839a1 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -319,32 +319,6 @@ static int expiration_struct2str(const void *obj, const intptr_t *args, char **b return (ast_asprintf(buf, "%ld", contact->expiration_time.tv_sec) < 0) ? -1 : 0; } -/*! \brief Helper function which validates a permanent contact */ -static int permanent_contact_validate(void *data) -{ - const char *value = data; - pj_pool_t *pool; - pj_str_t contact_uri; - static const pj_str_t HCONTACT = { "Contact", 7 }; - pjsip_contact_hdr *contact_hdr; - int rc = 0; - - pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Permanent Contact Validation", 256, 256); - if (!pool) { - return -1; - } - - pj_strdup2_with_null(pool, &contact_uri, value); - if (!(contact_hdr = pjsip_parse_hdr(pool, &HCONTACT, contact_uri.ptr, contact_uri.slen, NULL)) - || !(PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) - || PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri))) { - rc = -1; - } - - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); - return rc; -} - static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags) { const struct ast_sip_contact *object_left = obj_left; @@ -393,12 +367,6 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab struct ast_sip_contact_status *status; char contact_id[strlen(aor_id) + strlen(contact_uri) + 2 + 1]; - if (ast_sip_push_task_synchronous(NULL, permanent_contact_validate, contact_uri)) { - ast_log(LOG_ERROR, "Permanent URI on aor '%s' with contact '%s' failed to parse\n", - ast_sorcery_object_get_id(aor), contact_uri); - return -1; - } - if (!aor->permanent_contacts) { aor->permanent_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL); diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 2fdfc9d0b..bcbb791e6 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1084,29 +1084,6 @@ static struct ast_endpoint *persistent_endpoint_find_or_create(const struct ast_ return persistent->endpoint; } -/*! \brief Helper function which validates an outbound proxy */ -static int outbound_proxy_validate(void *data) -{ - const char *proxy = data; - pj_pool_t *pool; - pj_str_t tmp; - static const pj_str_t ROUTE_HNAME = { "Route", 5 }; - - pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound Proxy Validation", 256, 256); - if (!pool) { - return -1; - } - - pj_strdup2_with_null(pool, &tmp, proxy); - if (!pjsip_parse_hdr(pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL)) { - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); - return -1; - } - - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); - return 0; -} - /*! \brief Callback function for when an object is finalized */ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *obj) { @@ -1116,12 +1093,7 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o return -1; } - if (!ast_strlen_zero(endpoint->outbound_proxy) && - ast_sip_push_task_synchronous(NULL, outbound_proxy_validate, (char*)endpoint->outbound_proxy)) { - ast_log(LOG_ERROR, "Invalid outbound proxy '%s' specified on endpoint '%s'\n", - endpoint->outbound_proxy, ast_sorcery_object_get_id(endpoint)); - return -1; - } else if (endpoint->extensions.timer.min_se < 90) { + if (endpoint->extensions.timer.min_se < 90) { ast_log(LOG_ERROR, "Session timer minimum expires time must be 90 or greater on endpoint '%s'\n", ast_sorcery_object_get_id(endpoint)); return -1; diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c index 3dc8c1a8d..4e225dd1a 100644 --- a/res/res_pjsip_exten_state.c +++ b/res/res_pjsip_exten_state.c @@ -115,7 +115,7 @@ static void exten_state_subscription_destructor(void *obj) struct exten_state_subscription *sub = obj; ast_free(sub->user_agent); - ao2_cleanup(sub->sip_sub); + ast_sip_subscription_destroy(sub->sip_sub); ast_taskprocessor_unreference(sub->serializer); } @@ -160,7 +160,7 @@ static struct exten_state_subscription *exten_state_subscription_alloc( return NULL; } - exten_state_sub->sip_sub = ao2_bump(sip_sub); + exten_state_sub->sip_sub = sip_sub; /* We keep our own reference to the serializer as there is no guarantee in state_changed * that the subscription tree is still valid when it is called. This can occur when diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c index 06587daf7..f349324a4 100644 --- a/res/res_pjsip_mwi.c +++ b/res/res_pjsip_mwi.c @@ -204,7 +204,9 @@ static void mwi_subscription_destructor(void *obj) struct mwi_subscription *sub = obj; ast_debug(3, "Destroying MWI subscription for endpoint %s\n", sub->id); - ao2_cleanup(sub->sip_sub); + if (sub->is_solicited) { + ast_sip_subscription_destroy(sub->sip_sub); + } ao2_cleanup(sub->stasis_subs); ast_free(sub->aors); } @@ -233,7 +235,7 @@ static struct mwi_subscription *mwi_subscription_alloc(struct ast_sip_endpoint * * state not being updated on the device */ if (is_solicited) { - sub->sip_sub = ao2_bump(sip_sub); + sub->sip_sub = sip_sub; } sub->stasis_subs = ao2_container_alloc(STASIS_BUCKETS, stasis_sub_hash, stasis_sub_cmp); diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c index c4d02491b..b51a48a67 100644 --- a/res/res_pjsip_outbound_registration.c +++ b/res/res_pjsip_outbound_registration.c @@ -100,6 +100,21 @@ buggy registrars. </para></description> </configOption> + <configOption name="fatal_retry_interval" default="0"> + <synopsis>Interval used when receiving a Fatal response.</synopsis> + <description><para> + If a fatal response is received, chan_pjsip will wait + <replaceable>fatal_retry_interval</replaceable> seconds before + attempting registration again. If 0 is specified, chan_pjsip will not + retry after receiving a fatal (non-temporary 4xx, 5xx, 6xx) response. + Setting this to a non-zero value may go against a "SHOULD NOT" in RFC3261, + but can be used to work around buggy registrars.</para> + <note><para>if also set the <replaceable>forbidden_retry_interval</replaceable> + takes precedence over this one when a 403 is received. + Also, if <replaceable>auth_rejection_permanent</replaceable> equals 'yes' then + a 401 and 407 become subject to this retry interval.</para></note> + </description> + </configOption> <configOption name="server_uri"> <synopsis>SIP URI of the server to register against</synopsis> <description><para> @@ -277,6 +292,8 @@ struct sip_outbound_registration { unsigned int retry_interval; /*! \brief Interval at which retries should occur for permanent responses */ unsigned int forbidden_retry_interval; + /*! \brief Interval at which retries should occur for all permanent responses */ + unsigned int fatal_retry_interval; /*! \brief Treat authentication challenges that we cannot handle as permanent failures */ unsigned int auth_rejection_permanent; /*! \brief Maximum number of retries permitted */ @@ -312,6 +329,8 @@ struct sip_outbound_registration_client_state { unsigned int retry_interval; /*! \brief Interval at which retries should occur for permanent responses */ unsigned int forbidden_retry_interval; + /*! \brief Interval at which retries should occur for all permanent responses */ + unsigned int fatal_retry_interval; /*! \brief Treat authentication challenges that we cannot handle as permanent failures */ unsigned int auth_rejection_permanent; /*! \brief Determines whether SIP Path support should be advertised */ @@ -799,6 +818,14 @@ static int handle_registration_response(void *data) schedule_registration(response->client_state, response->client_state->forbidden_retry_interval); ast_log(LOG_WARNING, "403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", server_uri, client_uri, response->client_state->forbidden_retry_interval); + } else if (response->client_state->fatal_retry_interval + && response->client_state->retries < response->client_state->max_retries) { + /* Some kind of fatal failure response received, so retry according to configured interval */ + response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY; + response->client_state->retries++; + schedule_registration(response->client_state, response->client_state->fatal_retry_interval); + ast_log(LOG_WARNING, "'%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", + response->code, server_uri, client_uri, response->client_state->fatal_retry_interval); } else { /* Finally if there's no hope of registering give up */ response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT; @@ -1186,6 +1213,7 @@ static int sip_outbound_registration_perform(void *data) } state->client_state->retry_interval = registration->retry_interval; state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval; + state->client_state->fatal_retry_interval = registration->fatal_retry_interval; state->client_state->max_retries = registration->max_retries; state->client_state->retries = 0; state->client_state->support_path = registration->support_path; @@ -1909,6 +1937,7 @@ static int load_module(void) ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "60", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval)); + ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "fatal_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, fatal_retry_interval)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent)); ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0); diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index eb49aafd8..cbedaf28c 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -411,6 +411,8 @@ struct sip_subscription_tree { int is_list; /*! Next item in the list */ AST_LIST_ENTRY(sip_subscription_tree) next; + /*! Indicates that a NOTIFY is currently being sent on the SIP subscription */ + int last_notify; }; /*! @@ -596,6 +598,7 @@ static void subscription_persistence_remove(struct sip_subscription_tree *sub_tr ast_sorcery_delete(ast_sip_get_sorcery(), sub_tree->persistence); ao2_ref(sub_tree->persistence, -1); + sub_tree->persistence = NULL; } @@ -1020,26 +1023,6 @@ static int datastore_cmp(void *obj, void *arg, int flags) return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP; } -static int subscription_remove_serializer(void *obj) -{ - struct sip_subscription_tree *sub_tree = obj; - - /* This is why we keep the dialog on the subscription. When the subscription - * is destroyed, there is no guarantee that the underlying dialog is ready - * to be destroyed. Furthermore, there's no guarantee in the opposite direction - * either. The dialog could be destroyed before our subscription is. We fix - * this problem by keeping a reference to the dialog until it is time to - * destroy the subscription. We need to have the dialog available when the - * subscription is destroyed so that we can guarantee that our attempt to - * remove the serializer will be successful. - */ - ast_sip_dialog_set_serializer(sub_tree->dlg, NULL); - ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL); - pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module); - - return 0; -} - static void add_subscription(struct sip_subscription_tree *obj) { SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); @@ -1063,14 +1046,32 @@ static void remove_subscription(struct sip_subscription_tree *obj) AST_RWLIST_TRAVERSE_SAFE_END; } -static void subscription_destructor(void *obj) +static void destroy_subscription(struct ast_sip_subscription *sub) { - struct ast_sip_subscription *sub = obj; - ast_debug(3, "Destroying SIP subscription to resource %s\n", sub->resource); ast_free(sub->body_text); + AST_VECTOR_FREE(&sub->children); ao2_cleanup(sub->datastores); + ast_free(sub); +} + +static void destroy_subscriptions(struct ast_sip_subscription *root) +{ + int i; + + if (!root) { + return; + } + + for (i = 0; i < AST_VECTOR_SIZE(&root->children); ++i) { + struct ast_sip_subscription *child; + + child = AST_VECTOR_GET(&root->children, i); + destroy_subscriptions(child); + } + + destroy_subscription(root); } static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_subscription_handler *handler, @@ -1079,7 +1080,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s struct ast_sip_subscription *sub; pjsip_sip_uri *contact_uri; - sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor); + sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1); if (!sub) { return NULL; } @@ -1087,13 +1088,13 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp); if (!sub->datastores) { - ao2_ref(sub, -1); + destroy_subscription(sub); return NULL; } sub->body_text = ast_str_create(128); if (!sub->body_text) { - ao2_ref(sub, -1); + destroy_subscription(sub); return NULL; } @@ -1104,7 +1105,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s sub->handler = handler; sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE; - sub->tree = tree; + sub->tree = ao2_bump(tree); return sub; } @@ -1132,6 +1133,7 @@ static struct ast_sip_subscription *create_virtual_subscriptions(const struct as sub->full_state = current->full_state; sub->body_generator = generator; + AST_VECTOR_INIT(&sub->children, AST_VECTOR_SIZE(¤t->children)); for (i = 0; i < AST_VECTOR_SIZE(¤t->children); ++i) { struct ast_sip_subscription *child; @@ -1166,7 +1168,6 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub) if (AST_VECTOR_SIZE(&sub->children) > 0) { for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) { shutdown_subscriptions(AST_VECTOR_GET(&sub->children, i)); - ao2_cleanup(AST_VECTOR_GET(&sub->children, i)); } return; } @@ -1176,37 +1177,55 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub) sub->handler->subscription_shutdown(sub); } } +static int subscription_unreference_dialog(void *obj) +{ + struct sip_subscription_tree *sub_tree = obj; + + /* This is why we keep the dialog on the subscription. When the subscription + * is destroyed, there is no guarantee that the underlying dialog is ready + * to be destroyed. Furthermore, there's no guarantee in the opposite direction + * either. The dialog could be destroyed before our subscription is. We fix + * this problem by keeping a reference to the dialog until it is time to + * destroy the subscription. We need to have the dialog available when the + * subscription is destroyed so that we can guarantee that our attempt to + * remove the serializer will be successful. + */ + pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module); + sub_tree->dlg = NULL; + + return 0; +} static void subscription_tree_destructor(void *obj) { struct sip_subscription_tree *sub_tree = obj; + ast_debug(3, "Destroying subscription tree %p\n", sub_tree); + remove_subscription(sub_tree); - subscription_persistence_remove(sub_tree); ao2_cleanup(sub_tree->endpoint); - if (sub_tree->dlg) { - ast_sip_push_task_synchronous(NULL, subscription_remove_serializer, sub_tree); - } - - shutdown_subscriptions(sub_tree->root); - ao2_cleanup(sub_tree->root); + destroy_subscriptions(sub_tree->root); + ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree); ast_taskprocessor_unreference(sub_tree->serializer); ast_module_unref(ast_module_info->self); } +void ast_sip_subscription_destroy(struct ast_sip_subscription *sub) +{ + ast_debug(3, "Removing subscription %p reference to subscription tree %p\n", sub, sub->tree); + ao2_cleanup(sub->tree); +} + static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg) { - /* We keep a reference to the dialog until our subscription is destroyed. See - * the subscription_destructor for more details - */ - pjsip_dlg_inc_session(dlg, &pubsub_module); sub_tree->dlg = dlg; ast_sip_dialog_set_serializer(dlg, sub_tree->serializer); ast_sip_dialog_set_endpoint(dlg, sub_tree->endpoint); pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree); + pjsip_dlg_inc_session(dlg, &pubsub_module); } static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint) @@ -1497,7 +1516,11 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree, ast_str_append(buf, 0, "Endpoint: %s\r\n", ast_sorcery_object_get_id(sub_tree->endpoint)); - ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str)); + if (sub_tree->dlg) { + ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str)); + } else { + ast_copy_string(str, "<unknown>", sizeof(str)); + } ast_str_append(buf, 0, "Callid: %s\r\n", str); ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub)); @@ -1518,10 +1541,12 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree, void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header) { - pjsip_dialog *dlg = sub->tree->dlg; - pjsip_msg *msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG); + pjsip_dialog *dlg; + pjsip_msg *msg; pj_str_t name; + dlg = sub->tree->dlg; + msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG); pj_cstr(&name, header); return pjsip_msg_find_hdr_by_name(msg, &name, NULL); @@ -1654,6 +1679,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, #ifdef TEST_FRAMEWORK struct ast_sip_endpoint *endpoint = sub_tree->endpoint; #endif + pjsip_evsub *evsub = sub_tree->evsub; int res; if (allocate_tdata_buffer(tdata)) { @@ -1661,13 +1687,13 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, return -1; } - res = pjsip_evsub_send_request(sub_tree->evsub, tdata) == PJ_SUCCESS ? 0 : -1; + res = pjsip_evsub_send_request(evsub, tdata) == PJ_SUCCESS ? 0 : -1; subscription_persistence_update(sub_tree, NULL); ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET", "StateText: %s\r\n" "Endpoint: %s\r\n", - pjsip_evsub_get_state_name(sub_tree->evsub), + pjsip_evsub_get_state_name(evsub), ast_sorcery_object_get_id(endpoint)); return res; @@ -2075,6 +2101,8 @@ static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool) /*! * \brief Send a NOTIFY request to a subscriber * + * \pre sub_tree->dlg is locked + * * \param sub_tree The subscription tree representing the subscription * \param force_full_state If true, ignore resource list settings and send full resource list state. * \retval 0 Success @@ -2107,6 +2135,9 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require); } + if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) { + sub_tree->last_notify = 1; + } if (sip_subscription_send_request(sub_tree, tdata)) { return -1; } @@ -2119,13 +2150,16 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc static int serialized_send_notify(void *userdata) { struct sip_subscription_tree *sub_tree = userdata; + pjsip_dialog *dlg = sub_tree->dlg; + pjsip_dlg_inc_lock(dlg); /* It's possible that between when the notification was scheduled * and now, that a new SUBSCRIBE arrived, requiring full state to be * sent out in an immediate NOTIFY. If that has happened, we need to * bail out here instead of sending the batched NOTIFY. */ if (!sub_tree->send_scheduled_notify) { + pjsip_dlg_dec_lock(dlg); ao2_cleanup(sub_tree); return 0; } @@ -2135,6 +2169,7 @@ static int serialized_send_notify(void *userdata) "Resource: %s", sub_tree->root->resource); sub_tree->notify_sched_id = -1; + pjsip_dlg_dec_lock(dlg); ao2_cleanup(sub_tree); return 0; } @@ -2167,8 +2202,19 @@ static int schedule_notification(struct sip_subscription_tree *sub_tree) int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip_body_data *notify_data, int terminate) { + int res; + pjsip_dialog *dlg = sub->tree->dlg; + + pjsip_dlg_inc_lock(dlg); + + if (!sub->tree->evsub) { + pjsip_dlg_dec_lock(dlg); + return 0; + } + if (ast_sip_pubsub_generate_body_content(ast_sip_subscription_get_body_type(sub), ast_sip_subscription_get_body_subtype(sub), notify_data, &sub->body_text)) { + pjsip_dlg_dec_lock(dlg); return -1; } @@ -2178,9 +2224,8 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip } if (sub->tree->notification_batch_interval) { - return schedule_notification(sub->tree); + res = schedule_notification(sub->tree); } else { - int res; /* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */ ao2_ref(sub->tree, +1); res = send_notify(sub->tree, 0); @@ -2188,9 +2233,10 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip "Resource: %s", sub->tree->root->resource); ao2_ref(sub->tree, -1); - - return res; } + + pjsip_dlg_dec_lock(dlg); + return res; } void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size) @@ -2200,7 +2246,9 @@ void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char * void ast_sip_subscription_get_remote_uri(struct ast_sip_subscription *sub, char *buf, size_t size) { - pjsip_dialog *dlg = sub->tree->dlg; + pjsip_dialog *dlg; + + dlg = sub->tree->dlg; ast_copy_pj_str(buf, &dlg->remote.info_str, size); } @@ -3139,10 +3187,88 @@ static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata) return PJ_FALSE; } +static void set_state_terminated(struct ast_sip_subscription *sub) +{ + int i; + + sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED; + for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) { + set_state_terminated(AST_VECTOR_GET(&sub->children, i)); + } +} + +/* XXX This function and serialized_pubsub_on_rx_refresh are nearly identical */ +static int serialized_pubsub_on_server_timeout(void *userdata) +{ + struct sip_subscription_tree *sub_tree = userdata; + pjsip_dialog *dlg = sub_tree->dlg; + + pjsip_dlg_inc_lock(dlg); + if (!sub_tree->evsub) { + pjsip_dlg_dec_lock(dlg); + return 0; + } + set_state_terminated(sub_tree->root); + send_notify(sub_tree, 1); + ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED", + "Resource: %s", + sub_tree->root->resource); + + pjsip_dlg_dec_lock(dlg); + ao2_cleanup(sub_tree); + return 0; +} + +/*! + * \brief PJSIP callback when underlying SIP subscription changes state + * + * This callback is a bit of a mess, because it's not always called when + * you might expect it to be, and it can be called multiple times for the + * same state. + * + * For instance, this function is not called at all when an incoming SUBSCRIBE + * arrives to refresh a subscription. That makes sense in a way, since the + * subscription state has not made a change; it was active and remains active. + * + * However, if an incoming SUBSCRIBE arrives to end a subscription, then this + * will be called into once upon receiving the SUBSCRIBE (after the call to + * pubsub_on_rx_refresh) and again when sending a NOTIFY to end the subscription. + * In both cases, the apparent state of the subscription is "terminated". + * + * However, the double-terminated state changes don't happen in all cases. For + * instance, if a subscription expires, then the only time this callback is + * called is when we send the NOTIFY to end the subscription. + * + * As far as state changes are concerned, we only ever care about transitions + * to the "terminated" state. The action we take here is dependent on the + * conditions behind why the state change to "terminated" occurred. If the + * state change has occurred because we are sending a NOTIFY to end the + * subscription, we consider this to be the final hurrah of the subscription + * and take measures to start shutting things down. If the state change to + * terminated occurs for a different reason (e.g. transaction timeout, + * incoming SUBSCRIBE to end the subscription), then we push a task to + * send out a NOTIFY. When that NOTIFY is sent, this callback will be + * called again and we will actually shut down the subscription. The + * subscription tree's last_notify field let's us know if this is being + * called as a result of a terminating NOTIFY or not. + * + * There is no guarantee that this function will be called from a serializer + * thread since it can be called due to a transaction timeout. Therefore + * synchronization primitives are necessary to ensure that no operations + * step on each others' toes. The dialog lock is always held when this + * callback is called, so we ensure that relevant structures that may + * be touched in this function are always protected by the dialog lock + * elsewhere as well. The dialog lock in particular protects + * + * \li The subscription tree's last_notify field + * \li The subscription tree's evsub pointer + */ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event) { struct sip_subscription_tree *sub_tree; + ast_debug(3, "on_evsub_state called with state %s\n", pjsip_evsub_get_state_name(evsub)); + if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) { return; } @@ -3152,21 +3278,63 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event) return; } - ao2_cleanup(sub_tree); + if (!sub_tree->last_notify) { + if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_server_timeout, ao2_bump(sub_tree))) { + ast_log(LOG_ERROR, "Failed to push task to send final NOTIFY.\n"); + ao2_ref(sub_tree, -1); + } else { + return; + } + } pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL); + sub_tree->evsub = NULL; + ast_sip_dialog_set_serializer(sub_tree->dlg, NULL); + ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL); + subscription_persistence_remove(sub_tree); + shutdown_subscriptions(sub_tree->root); + + /* Remove evsub's reference to the sub_tree */ + ao2_ref(sub_tree, -1); } -static void set_state_terminated(struct ast_sip_subscription *sub) +static int serialized_pubsub_on_rx_refresh(void *userdata) { - int i; + struct sip_subscription_tree *sub_tree = userdata; + pjsip_dialog *dlg = sub_tree->dlg; - sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED; - for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) { - set_state_terminated(AST_VECTOR_GET(&sub->children, i)); + pjsip_dlg_inc_lock(dlg); + if (!sub_tree->evsub) { + pjsip_dlg_dec_lock(dlg); + return 0; + } + + if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) { + set_state_terminated(sub_tree->root); } + + send_notify(sub_tree, 1); + + ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ? + "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED", + "Resource: %s", sub_tree->root->resource); + + pjsip_dlg_dec_lock(dlg); + ao2_cleanup(sub_tree); + return 0; } +/*! + * \brief Called whenever an in-dialog SUBSCRIBE is received + * + * This includes both SUBSCRIBE requests that actually refresh the subscription + * as well as SUBSCRIBE requests that end the subscription. + * + * In the case where the SUBSCRIBE is actually refreshing the subscription we + * push a task to send an appropriate NOTIFY request. In the case where the + * SUBSCRIBE is ending the subscription, we let the pubsub_on_evsub_state + * callback take care of sending the terminal NOTIFY request instead. + */ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) { @@ -3177,31 +3345,19 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata, return; } - /* If sending a NOTIFY to terminate a subscription, then pubsub_on_evsub_state() - * will be called when we send the NOTIFY, and that will result in dropping the - * refcount of sub_tree by one, and possibly destroying the sub_tree. We need to - * hold a reference to the sub_tree until this function returns so that we don't - * try to read from or write to freed memory by accident + /* PJSIP will set the evsub's state to terminated before calling into this function + * if the Expires value of the incoming SUBSCRIBE is 0. */ - ao2_ref(sub_tree, +1); - - if (pjsip_evsub_get_state(evsub) == PJSIP_EVSUB_STATE_TERMINATED) { - set_state_terminated(sub_tree->root); - } - - if (send_notify(sub_tree, 1)) { - *p_st_code = 500; + if (pjsip_evsub_get_state(sub_tree->evsub) != PJSIP_EVSUB_STATE_TERMINATED) { + if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_rx_refresh, ao2_bump(sub_tree))) { + /* If we can't push the NOTIFY refreshing task...we'll just go with it. */ + ao2_ref(sub_tree, -1); + } } - ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ? - "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED", - "Resource: %s", sub_tree->root->resource); - if (sub_tree->is_list) { pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool)); } - - ao2_ref(sub_tree, -1); } static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code, @@ -3239,31 +3395,24 @@ static void pubsub_on_client_refresh(pjsip_evsub *evsub) ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, sub_tree); } -static int serialized_pubsub_on_server_timeout(void *userdata) -{ - struct sip_subscription_tree *sub_tree = userdata; - - set_state_terminated(sub_tree->root); - send_notify(sub_tree, 1); - ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED", - "Resource: %s", - sub_tree->root->resource); - - ao2_cleanup(sub_tree); - return 0; -} - static void pubsub_on_server_timeout(pjsip_evsub *evsub) { - struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); + struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); if (!sub_tree) { - /* if a subscription has been terminated and the subscription - timeout/expires is less than the time it takes for all pending - transactions to end then the subscription timer will not have - been canceled yet and sub will be null, so do nothing since - the subscription has already been terminated. */ - return; + /* PJSIP does not terminate the server timeout timer when a SUBSCRIBE + * with Expires: 0 arrives to end a subscription, nor does it terminate + * this timer when we send a NOTIFY request in response to receiving such + * a SUBSCRIBE. PJSIP does not stop the server timeout timer until the + * NOTIFY transaction has finished (either through receiving a response + * or through a transaction timeout). + * + * Therefore, it is possible that we can be told that a server timeout + * occurred after we already thought that the subscription had been + * terminated. In such a case, we will have already removed the sub_tree + * from the evsub's mod_data array. + */ + return; } ao2_ref(sub_tree, +1); diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in index 58702d6c4..661652489 100644 --- a/res/res_pjsip_pubsub.exports.in +++ b/res/res_pjsip_pubsub.exports.in @@ -38,6 +38,7 @@ LINKER_SYMBOL_PREFIXast_sip_subscription_get_remote_uri; LINKER_SYMBOL_PREFIXast_sip_subscription_get_header; LINKER_SYMBOL_PREFIXast_sip_subscription_is_terminated; + LINKER_SYMBOL_PREFIXast_sip_subscription_destroy; local: *; }; diff --git a/rest-api-templates/api.wiki.mustache b/rest-api-templates/api.wiki.mustache index 73aa2448a..0a54a64a7 100644 --- a/rest-api-templates/api.wiki.mustache +++ b/rest-api-templates/api.wiki.mustache @@ -67,7 +67,7 @@ h3. Header parameters h3. Error Responses {{#error_responses}} -* {{code}} - {{{reason}}} +* {{code}} - {{{wiki_reason}}} {{/error_responses}} {{/has_error_responses}} {{/operations}} diff --git a/rest-api-templates/asterisk_processor.py b/rest-api-templates/asterisk_processor.py index ab8a8afd2..68a679994 100644 --- a/rest-api-templates/asterisk_processor.py +++ b/rest-api-templates/asterisk_processor.py @@ -199,6 +199,8 @@ class AsteriskProcessor(SwaggerPostProcessor): raise SwaggerError("Summary should end with .", context) operation.wiki_summary = wikify(operation.summary or "") operation.wiki_notes = wikify(operation.notes or "") + for error_response in operation.error_responses: + error_response.wiki_reason = wikify(error_response.reason or "") operation.parse_body = (operation.body_parameter or operation.has_query_parameters) and True def process_parameter(self, parameter, context): |