diff options
90 files changed, 2880 insertions, 899 deletions
@@ -12,6 +12,41 @@ --- Functionality changes from Asterisk 13.9.0 to Asterisk 13.10.0 ----------- ------------------------------------------------------------------------------ +Core +------------------ + * A channel variable FORWARDERNAME is now set which indicates which channel + was responsible for a forwarding requests received on dial attempt. + +chan_multicast_rtp +------------------ + * Deprecated in favor of chan_rtp which is basically chan_multicast_rtp + renamed to chan_rtp with UnicastRTP channels added and some internal code + improvements. + +chan_rtp +------------------ + * The format for dialing a unicast RTP channel is: + UnicastRTP/<destination-addr>[/[<options>]] + Where <destination-addr> is something like '127.0.0.1:5060'. + Where <options> are in standard Asterisk flag options format: + c(<codec>) - Specify which codec/format to use such as 'ulaw'. + e(<engine>) - Specify which RTP engine to use such as 'asterisk'. + + * More options are available over what chan_multicast_rtp supports. + The format for dialing a multicast RTP channel is: + MulticastRTP/<type>/<destination-addr>[/[<control-addr>][/[<options>]]] + Where <type> can be either 'basic' or 'linksys'. + Where <destination-addr> is something like '224.0.0.3:5060'. + Where <control-addr> is something like '127.0.0.1:5060'. + Where <options> are in standard Asterisk flag options format: + c(<codec>) - Specify which codec/format to use such as 'ulaw'. + i(<address>) - Specify the interface address from which multicast RTP + is sent. + l(<enable>) - Set whether packets are looped back to the sender. The + enable value can be 0 to set looping to off and non-zero to set + looping on. + t(<ttl>) - Set the time-to-live (TTL) value for multicast packets. + func_odbc ------------------ * Added new global option "single_db_connection". @@ -47,6 +82,9 @@ res_pjsip into the "reg_server" field in the ps_contacts table to facilitate multi-server setups. + * When starting Asterisk, received traffic will now be ignored until Asterisk + has loaded all modules and is fully booted. + res_hep ------------------ * Added a new option, 'uuid_type', that sets the preferred source of the Homer @@ -63,6 +101,12 @@ res_pjsip_info_empty Some SBCs will terminate a call if their empty INFO packets are not responded to within a predefined time. +res_odbc +------------------ + * A new option has been added, 'max_connections', which sets the maximum number + of concurrent connections to the database. This option defaults to 1 which + returns the behavior to that of Asterisk 13.7 and prior. + app_confbridge ------------------ * Added a bridge profile option called regcontext that allows you to @@ -71,6 +115,18 @@ app_confbridge server installations via alternate means (DUNDI for example). By default this feature is not used. +Codecs +------------------ + * Added the associated format name to 'core show codecs'. + +res_ari_channels +------------------ + * Added 'formats' to channel create/originate to allow setting the allowed + formats for a channel when no originator channel is available. Especially + useful for Local channel creation where no other format information is + available. 'core show codecs' can now be used to look up suitable format + names. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 13.8.0 to Asterisk 13.9.0 ------------ ------------------------------------------------------------------------------ diff --git a/addons/ooh323c/src/Makefile.in b/addons/ooh323c/src/Makefile.in index d3a96024b..15b14f7df 100644 --- a/addons/ooh323c/src/Makefile.in +++ b/addons/ooh323c/src/Makefile.in @@ -104,7 +104,7 @@ CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = LIBRARIES = $(noinst_LIBRARIES) -libooh323c_a_AR = $(AR) cru +libooh323c_a_AR = $(AR) cr libooh323c_a_LIBADD = am_libooh323c_a_OBJECTS = ooLogChan.$(OBJEXT) ooUtils.$(OBJEXT) \ ooGkClient.$(OBJEXT) context.$(OBJEXT) ooDateTime.$(OBJEXT) \ diff --git a/apps/app_dial.c b/apps/app_dial.c index bc4f8a574..c05aecaf9 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -834,6 +834,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num, struct ast_party_id *forced_clid, struct ast_party_id *stored_clid) { char tmpchan[256]; + char forwarder[AST_CHANNEL_NAME]; struct ast_channel *original = o->chan; struct ast_channel *c = o->chan; /* the winner */ struct ast_channel *in = num->chan; /* the input channel */ @@ -842,6 +843,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num, int cause; struct ast_party_caller caller; + ast_copy_string(forwarder, ast_channel_name(c), sizeof(forwarder)); ast_copy_string(tmpchan, ast_channel_call_forward(c), sizeof(tmpchan)); if ((stuff = strchr(tmpchan, '/'))) { *stuff++ = '\0'; @@ -893,6 +895,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num, ast_channel_lock_both(in, o->chan); ast_channel_inherit_variables(in, o->chan); ast_channel_datastore_inherit(in, o->chan); + pbx_builtin_setvar_helper(o->chan, "FORWARDERNAME", forwarder); ast_max_forwards_decrement(o->chan); ast_channel_unlock(in); ast_channel_unlock(o->chan); diff --git a/apps/app_queue.c b/apps/app_queue.c index dbd83938d..3d22f9821 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -4827,16 +4827,22 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte continue; } else if (!ast_strlen_zero(ast_channel_call_forward(o->chan))) { struct ast_channel *original = o->chan; + char forwarder[AST_CHANNEL_NAME]; char tmpchan[256]; char *stuff; char *tech; ast_copy_string(tmpchan, ast_channel_call_forward(o->chan), sizeof(tmpchan)); + ast_copy_string(forwarder, ast_channel_name(o->chan), sizeof(forwarder)); if ((stuff = strchr(tmpchan, '/'))) { *stuff++ = '\0'; tech = tmpchan; } else { - snprintf(tmpchan, sizeof(tmpchan), "%s@%s", ast_channel_call_forward(o->chan), ast_channel_context(o->chan)); + const char *forward_context; + ast_channel_lock(o->chan); + forward_context = pbx_builtin_getvar_helper(o->chan, "FORWARD_CONTEXT"); + snprintf(tmpchan, sizeof(tmpchan), "%s@%s", ast_channel_call_forward(o->chan), forward_context ? forward_context : ast_channel_context(o->chan)); + ast_channel_unlock(o->chan); stuff = tmpchan; tech = "Local"; } @@ -4868,6 +4874,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte ast_channel_lock_both(o->chan, in); ast_channel_inherit_variables(in, o->chan); ast_channel_datastore_inherit(in, o->chan); + pbx_builtin_setvar_helper(o->chan, "FORWARDERNAME", forwarder); ast_max_forwards_decrement(o->chan); if (o->pending_connected_update) { diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index a561729fb..e88d7069c 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -3263,7 +3263,8 @@ void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes) void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status) { struct ast_str *str; - if (!DEBUG_ATLEAST(5) || !(str = ast_str_create(MAX_OBJECT_FIELD))) { + + if (!DEBUG_ATLEAST(5) || !(str = ast_str_create(256))) { return; } diff --git a/autoconf/ast_ext_lib.m4 b/autoconf/ast_ext_lib.m4 index 8f35f4bad..2c73b40c5 100644 --- a/autoconf/ast_ext_lib.m4 +++ b/autoconf/ast_ext_lib.m4 @@ -11,7 +11,7 @@ AC_DEFUN([AST_EXT_LIB_SETUP], $1_DESCRIP="$2" $1_OPTION="$3" PBX_$1=0 - AC_ARG_WITH([$3], AC_HELP_STRING([--with-$3=PATH],[use $2 files in PATH$4]), + AC_ARG_WITH([$3], AS_HELP_STRING([--with-$3=PATH],[use $2 files in PATH$4]), [ case ${withval} in n|no) diff --git a/autoconf/ast_prog_ld.m4 b/autoconf/ast_prog_ld.m4 index 9177fedb3..b69c2c2ac 100644 --- a/autoconf/ast_prog_ld.m4 +++ b/autoconf/ast_prog_ld.m4 @@ -3,7 +3,7 @@ # find the pathname to the GNU or non-GNU linker AC_DEFUN([AST_PROG_LD], [AC_ARG_WITH([gnu-ld], - [AC_HELP_STRING([--with-gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], [assume the C compiler uses GNU ld @<:@default=no@:>@])], [test "$withval" = no || with_gnu_ld=yes], [with_gnu_ld=no]) diff --git a/channels/chan_multicast_rtp.c b/channels/chan_multicast_rtp.c index 267baabf1..c45dedf7f 100644 --- a/channels/chan_multicast_rtp.c +++ b/channels/chan_multicast_rtp.c @@ -28,7 +28,8 @@ */ /*** MODULEINFO - <support_level>core</support_level> + <support_level>deprecated</support_level> + <defaultenabled>no</defaultenabled> ***/ #include "asterisk.h" @@ -215,8 +216,8 @@ static int unload_module(void) return 0; } -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Multicast RTP Paging Channel", - .support_level = AST_MODULE_SUPPORT_CORE, +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Multicast RTP Paging Channel (use chan_rtp instead)", + .support_level = AST_MODULE_SUPPORT_DEPRECATED, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index f081bd899..970fef496 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -269,6 +269,9 @@ static int direct_media_mitigate_glare(struct ast_sip_session *session) return 0; } +/*! + * \pre chan is locked + */ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_sip_session_media *media, int rtcp_fd) { @@ -338,6 +341,11 @@ static int send_direct_media_request(void *data) int changed = 0; int res = 0; + /* The channel needs to be locked when checking for RTP changes. + * Otherwise, we could end up destroying an underlying RTCP structure + * at the same time that the channel thread is attempting to read RTCP + */ + ast_channel_lock(cdata->chan); if (pvt->media[SIP_MEDIA_AUDIO]) { changed |= check_for_rtp_changes( cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1); @@ -346,6 +354,7 @@ static int send_direct_media_request(void *data) changed |= check_for_rtp_changes( cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3); } + ast_channel_unlock(cdata->chan); if (direct_media_mitigate_glare(cdata->session)) { ast_debug(4, "Disregarding setting RTP on %s: mitigating re-INVITE glare\n", ast_channel_name(cdata->chan)); diff --git a/channels/chan_rtp.c b/channels/chan_rtp.c new file mode 100644 index 000000000..0fe66bd20 --- /dev/null +++ b/channels/chan_rtp.c @@ -0,0 +1,415 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009 - 2014, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.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 + * + * \author Joshua Colp <jcolp@digium.com> + * \author Andreas 'MacBrody' Broadmann <andreas.brodmann@gmail.com> + * + * \brief RTP (Multicast and Unicast) Media Channel + * + * \ingroup channel_drivers + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_REGISTER_FILE() + +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" +#include "asterisk/app.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/causes.h" +#include "asterisk/format_cache.h" +#include "asterisk/multicast_rtp.h" + +/* Forward declarations */ +static struct ast_channel *multicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); +static struct ast_channel *unicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); +static int rtp_call(struct ast_channel *ast, const char *dest, int timeout); +static int rtp_hangup(struct ast_channel *ast); +static struct ast_frame *rtp_read(struct ast_channel *ast); +static int rtp_write(struct ast_channel *ast, struct ast_frame *f); + +/* Multicast channel driver declaration */ +static struct ast_channel_tech multicast_rtp_tech = { + .type = "MulticastRTP", + .description = "Multicast RTP Paging Channel Driver", + .requester = multicast_rtp_request, + .call = rtp_call, + .hangup = rtp_hangup, + .read = rtp_read, + .write = rtp_write, +}; + +/* Unicast channel driver declaration */ +static struct ast_channel_tech unicast_rtp_tech = { + .type = "UnicastRTP", + .description = "Unicast RTP Media Channel Driver", + .requester = unicast_rtp_request, + .call = rtp_call, + .hangup = rtp_hangup, + .read = rtp_read, + .write = rtp_write, +}; + +/*! \brief Function called when we should read a frame from the channel */ +static struct ast_frame *rtp_read(struct ast_channel *ast) +{ + struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); + int fdno = ast_channel_fdno(ast); + + switch (fdno) { + case 0: + return ast_rtp_instance_read(instance, 0); + default: + return &ast_null_frame; + } +} + +/*! \brief Function called when we should write a frame to the channel */ +static int rtp_write(struct ast_channel *ast, struct ast_frame *f) +{ + struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); + + return ast_rtp_instance_write(instance, f); +} + +/*! \brief Function called when we should actually call the destination */ +static int rtp_call(struct ast_channel *ast, const char *dest, int timeout) +{ + struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); + + ast_queue_control(ast, AST_CONTROL_ANSWER); + + return ast_rtp_instance_activate(instance); +} + +/*! \brief Function called when we should hang the channel up */ +static int rtp_hangup(struct ast_channel *ast) +{ + struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); + + ast_rtp_instance_destroy(instance); + + ast_channel_tech_pvt_set(ast, NULL); + + return 0; +} + +/*! \brief Function called when we should prepare to call the multicast destination */ +static struct ast_channel *multicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) +{ + char *parse; + struct ast_rtp_instance *instance; + struct ast_sockaddr control_address; + struct ast_sockaddr destination_address; + struct ast_channel *chan; + struct ast_format_cap *caps = NULL; + struct ast_format *fmt = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(type); + AST_APP_ARG(destination); + AST_APP_ARG(control); + AST_APP_ARG(options); + ); + struct ast_multicast_rtp_options *mcast_options = NULL; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "A multicast type and destination must be given to the 'MulticastRTP' channel\n"); + goto failure; + } + parse = ast_strdupa(data); + AST_NONSTANDARD_APP_ARGS(args, parse, '/'); + + if (ast_strlen_zero(args.type)) { + ast_log(LOG_ERROR, "Type is required for the 'MulticastRTP' channel\n"); + goto failure; + } + + if (ast_strlen_zero(args.destination)) { + ast_log(LOG_ERROR, "Destination is required for the 'MulticastRTP' channel\n"); + goto failure; + } + if (!ast_sockaddr_parse(&destination_address, args.destination, PARSE_PORT_REQUIRE)) { + ast_log(LOG_ERROR, "Destination address '%s' could not be parsed\n", + args.destination); + goto failure; + } + + ast_sockaddr_setnull(&control_address); + if (!ast_strlen_zero(args.control) + && !ast_sockaddr_parse(&control_address, args.control, PARSE_PORT_REQUIRE)) { + ast_log(LOG_ERROR, "Control address '%s' could not be parsed\n", args.control); + goto failure; + } + + mcast_options = ast_multicast_rtp_create_options(args.type, args.options); + if (!mcast_options) { + goto failure; + } + + fmt = ast_multicast_rtp_options_get_format(mcast_options); + if (!fmt) { + fmt = ast_format_cap_get_format(cap, 0); + } + if (!fmt) { + ast_log(LOG_ERROR, "No codec available for sending RTP to '%s'\n", + args.destination); + goto failure; + } + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + goto failure; + } + + instance = ast_rtp_instance_new("multicast", NULL, &control_address, mcast_options); + if (!instance) { + ast_log(LOG_ERROR, + "Could not create '%s' multicast RTP instance for sending media to '%s'\n", + args.type, args.destination); + goto failure; + } + + chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids, + requestor, 0, "MulticastRTP/%p", instance); + if (!chan) { + ast_rtp_instance_destroy(instance); + goto failure; + } + ast_rtp_instance_set_channel_id(instance, ast_channel_uniqueid(chan)); + ast_rtp_instance_set_remote_address(instance, &destination_address); + + ast_channel_tech_set(chan, &multicast_rtp_tech); + + ast_format_cap_append(caps, fmt, 0); + ast_channel_nativeformats_set(chan, caps); + ast_channel_set_writeformat(chan, fmt); + ast_channel_set_rawwriteformat(chan, fmt); + ast_channel_set_readformat(chan, fmt); + ast_channel_set_rawreadformat(chan, fmt); + + ast_channel_tech_pvt_set(chan, instance); + + ast_channel_unlock(chan); + + ao2_ref(fmt, -1); + ao2_ref(caps, -1); + ast_multicast_rtp_free_options(mcast_options); + + return chan; + +failure: + ao2_cleanup(fmt); + ao2_cleanup(caps); + ast_multicast_rtp_free_options(mcast_options); + *cause = AST_CAUSE_FAILURE; + return NULL; +} + +enum { + OPT_RTP_CODEC = (1 << 0), + OPT_RTP_ENGINE = (1 << 1), +}; + +enum { + OPT_ARG_RTP_CODEC, + OPT_ARG_RTP_ENGINE, + /* note: this entry _MUST_ be the last one in the enum */ + OPT_ARG_ARRAY_SIZE +}; + +AST_APP_OPTIONS(unicast_rtp_options, BEGIN_OPTIONS + /*! Set the codec to be used for unicast RTP */ + AST_APP_OPTION_ARG('c', OPT_RTP_CODEC, OPT_ARG_RTP_CODEC), + /*! Set the RTP engine to use for unicast RTP */ + AST_APP_OPTION_ARG('e', OPT_RTP_ENGINE, OPT_ARG_RTP_ENGINE), +END_OPTIONS ); + +/*! \brief Function called when we should prepare to call the unicast destination */ +static struct ast_channel *unicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) +{ + char *parse; + struct ast_rtp_instance *instance; + struct ast_sockaddr address; + struct ast_sockaddr local_address; + struct ast_channel *chan; + struct ast_format_cap *caps = NULL; + struct ast_format *fmt = NULL; + const char *engine_name; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(destination); + AST_APP_ARG(options); + ); + struct ast_flags opts = { 0, }; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Destination is required for the 'UnicastRTP' channel\n"); + goto failure; + } + parse = ast_strdupa(data); + AST_NONSTANDARD_APP_ARGS(args, parse, '/'); + + if (ast_strlen_zero(args.destination)) { + ast_log(LOG_ERROR, "Destination is required for the 'UnicastRTP' channel\n"); + goto failure; + } + if (!ast_sockaddr_parse(&address, args.destination, PARSE_PORT_REQUIRE)) { + ast_log(LOG_ERROR, "Destination '%s' could not be parsed\n", args.destination); + goto failure; + } + + if (!ast_strlen_zero(args.options) + && ast_app_parse_options(unicast_rtp_options, &opts, opt_args, + ast_strdupa(args.options))) { + ast_log(LOG_ERROR, "'UnicastRTP' channel options '%s' parse error\n", + args.options); + goto failure; + } + + if (ast_test_flag(&opts, OPT_RTP_CODEC) + && !ast_strlen_zero(opt_args[OPT_ARG_RTP_CODEC])) { + fmt = ast_format_cache_get(opt_args[OPT_ARG_RTP_CODEC]); + if (!fmt) { + ast_log(LOG_ERROR, "Codec '%s' not found for sending RTP to '%s'\n", + opt_args[OPT_ARG_RTP_CODEC], args.destination); + goto failure; + } + } else { + fmt = ast_format_cap_get_format(cap, 0); + if (!fmt) { + ast_log(LOG_ERROR, "No codec available for sending RTP to '%s'\n", + args.destination); + goto failure; + } + } + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + goto failure; + } + + engine_name = S_COR(ast_test_flag(&opts, OPT_RTP_ENGINE), + opt_args[OPT_ARG_RTP_ENGINE], NULL); + + ast_ouraddrfor(&address, &local_address); + instance = ast_rtp_instance_new(engine_name, NULL, &local_address, NULL); + if (!instance) { + ast_log(LOG_ERROR, + "Could not create %s RTP instance for sending media to '%s'\n", + S_OR(engine_name, "default"), args.destination); + goto failure; + } + + chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids, + requestor, 0, "UnicastRTP/%s-%p", args.destination, instance); + if (!chan) { + ast_rtp_instance_destroy(instance); + goto failure; + } + ast_rtp_instance_set_channel_id(instance, ast_channel_uniqueid(chan)); + ast_rtp_instance_set_remote_address(instance, &address); + ast_channel_set_fd(chan, 0, ast_rtp_instance_fd(instance, 0)); + + ast_channel_tech_set(chan, &unicast_rtp_tech); + + ast_format_cap_append(caps, fmt, 0); + ast_channel_nativeformats_set(chan, caps); + ast_channel_set_writeformat(chan, fmt); + ast_channel_set_rawwriteformat(chan, fmt); + ast_channel_set_readformat(chan, fmt); + ast_channel_set_rawreadformat(chan, fmt); + + ast_channel_tech_pvt_set(chan, instance); + + pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_ADDRESS", + ast_sockaddr_stringify_addr(&local_address)); + ast_rtp_instance_get_local_address(instance, &local_address); + pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_PORT", + ast_sockaddr_stringify_port(&local_address)); + + ast_channel_unlock(chan); + + ao2_ref(fmt, -1); + ao2_ref(caps, -1); + + return chan; + +failure: + ao2_cleanup(fmt); + ao2_cleanup(caps); + *cause = AST_CAUSE_FAILURE; + return NULL; +} + +/*! \brief Function called when our module is unloaded */ +static int unload_module(void) +{ + ast_channel_unregister(&multicast_rtp_tech); + ao2_cleanup(multicast_rtp_tech.capabilities); + multicast_rtp_tech.capabilities = NULL; + + ast_channel_unregister(&unicast_rtp_tech); + ao2_cleanup(unicast_rtp_tech.capabilities); + unicast_rtp_tech.capabilities = NULL; + + return 0; +} + +/*! \brief Function called when our module is loaded */ +static int load_module(void) +{ + if (!(multicast_rtp_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return AST_MODULE_LOAD_DECLINE; + } + ast_format_cap_append_by_type(multicast_rtp_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); + if (ast_channel_register(&multicast_rtp_tech)) { + ast_log(LOG_ERROR, "Unable to register channel class 'MulticastRTP'\n"); + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + if (!(unicast_rtp_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + ast_format_cap_append_by_type(unicast_rtp_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); + if (ast_channel_register(&unicast_rtp_tech)) { + ast_log(LOG_ERROR, "Unable to register channel class 'UnicastRTP'\n"); + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "RTP Media Channel", + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DRIVER, +); diff --git a/channels/chan_sip.c b/channels/chan_sip.c index f64845472..a5aa6c3d3 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -11332,25 +11332,7 @@ static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_ ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); } - if (ast_format_cmp(format, ast_format_siren7) == AST_FORMAT_CMP_EQUAL) { - if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { - if (bit_rate != 32000) { - ast_log(LOG_WARNING, "Got Siren7 offer at %u bps, but only 32000 bps supported; ignoring.\n", bit_rate); - ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); - } else { - found = TRUE; - } - } - } else if (ast_format_cmp(format, ast_format_siren14) == AST_FORMAT_CMP_EQUAL) { - if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { - if (bit_rate != 48000) { - ast_log(LOG_WARNING, "Got Siren14 offer at %u bps, but only 48000 bps supported; ignoring.\n", bit_rate); - ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); - } else { - found = TRUE; - } - } - } else if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { + if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { if (bit_rate != 64000) { ast_log(LOG_WARNING, "Got G.719 offer at %u bps, but only 64000 bps supported; ignoring.\n", bit_rate); @@ -13009,12 +12991,6 @@ static void add_codec_to_sdp(const struct sip_pvt *p, } else if (ast_format_cmp(format, ast_format_g723) == AST_FORMAT_CMP_EQUAL) { /* Indicate that we don't support VAD (G.723.1 annex A) */ ast_str_append(a_buf, 0, "a=fmtp:%d annexa=no\r\n", rtp_code); - } else if (ast_format_cmp(format, ast_format_siren7) == AST_FORMAT_CMP_EQUAL) { - /* Indicate that we only expect 32Kbps */ - ast_str_append(a_buf, 0, "a=fmtp:%d bitrate=32000\r\n", rtp_code); - } else if (ast_format_cmp(format, ast_format_siren14) == AST_FORMAT_CMP_EQUAL) { - /* Indicate that we only expect 48Kbps */ - ast_str_append(a_buf, 0, "a=fmtp:%d bitrate=48000\r\n", rtp_code); } else if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { /* Indicate that we only expect 64Kbps */ ast_str_append(a_buf, 0, "a=fmtp:%d bitrate=64000\r\n", rtp_code); @@ -14154,9 +14130,10 @@ static void build_contact(struct sip_pvt *p, struct sip_request *req, int incomi /*! \brief Initiate new SIP request to peer/user */ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri) { - struct ast_str *invite = ast_str_alloca(256); - char from[256]; - char to[256]; +#define SIPHEADER 256 + struct ast_str *invite = ast_str_create(SIPHEADER); + struct ast_str *from = ast_str_create(SIPHEADER); + struct ast_str *to = ast_str_create(SIPHEADER); char tmp_n[SIPBUFSIZE/2]; /* build a local copy of 'n' if needed */ char tmp_l[SIPBUFSIZE/2]; /* build a local copy of 'l' if needed */ const char *l = NULL; /* XXX what is this, exactly ? */ @@ -14258,34 +14235,40 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho ourport = (p->fromdomainport && (p->fromdomainport != STANDARD_SIP_PORT)) ? p->fromdomainport : ast_sockaddr_port(&p->ourip); if (!sip_standard_port(p->socket.type, ourport)) { - ret = snprintf(from, sizeof(from), "<sip:%s@%s:%d>;tag=%s", tmp_l, d, ourport, p->tag); + ret = ast_str_set(&from, 0, "<sip:%s@%s:%d>;tag=%s", tmp_l, d, ourport, p->tag); } else { - ret = snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", tmp_l, d, p->tag); + ret = ast_str_set(&from, 0, "<sip:%s@%s>;tag=%s", tmp_l, d, p->tag); } - if (ret < 0 || ret >= sizeof(from)) { /* a return value of size or more means that the output was truncated */ + if (ret == AST_DYNSTR_BUILD_FAILED) { /* We don't have an escape path from here... */ ast_log(LOG_ERROR, "The From header was truncated in call '%s'. This call setup will fail.\n", p->callid); + /* Make sure that the field contains something non-broken. + See https://issues.asterisk.org/jira/browse/ASTERISK-26069 + */ + ast_str_set(&from, 3, "<>"); + } /* If a caller id name was specified, prefix a display name, if there is enough room. */ if (cid_has_name || !cid_has_num) { - size_t written = strlen(from); - ssize_t left = sizeof(from) - written - 4; /* '"" \0' */ - if (left > 0) { - size_t name_len; - if (sip_cfg.pedanticsipchecking) { - ast_escape_quoted(n, tmp_n, MIN(left + 1, sizeof(tmp_n))); - n = tmp_n; - } - name_len = strlen(n); - if (left < name_len) { - name_len = left; - } - memmove(from + name_len + 3, from, written + 1); - from[0] = '"'; - memcpy(from + 1, n, name_len); - from[name_len + 1] = '"'; - from[name_len + 2] = ' '; + size_t written = ast_str_strlen(from); + size_t name_len; + if (sip_cfg.pedanticsipchecking) { + ast_escape_quoted(n, tmp_n, sizeof(tmp_n)); + n = tmp_n; + } + name_len = strlen(n); + ret = ast_str_make_space(&from, name_len + written + 4); + + if (ret == 0) { + /* needed again, as ast_str_make_space coud've changed the pointer */ + char *from_buf = ast_str_buffer(from); + + memmove(from_buf + name_len + 3, from_buf, written + 1); + from_buf[0] = '"'; + memcpy(from_buf + 1, n, name_len); + from_buf[name_len + 1] = '"'; + from_buf[name_len + 2] = ' '; } } @@ -14328,24 +14311,28 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho /*! \todo Need to add back the VXML URL here at some point, possibly use build_string for all this junk */ if (!strchr(p->todnid, '@')) { /* We have no domain in the dnid */ - ret = snprintf(to, sizeof(to), "<sip:%s@%s>%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); + ret = ast_str_set(&to, 0, "<sip:%s@%s>%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); } else { - ret = snprintf(to, sizeof(to), "<sip:%s>%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); + ret = ast_str_set(&to, 0, "<sip:%s>%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); } } else { if (sipmethod == SIP_NOTIFY && !ast_strlen_zero(p->theirtag)) { /* If this is a NOTIFY, use the From: tag in the subscribe (RFC 3265) */ - ret = snprintf(to, sizeof(to), "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "sip:" : ""), p->uri, p->theirtag); + ret = ast_str_set(&to, 0, "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "sip:" : ""), p->uri, p->theirtag); } else if (p->options && p->options->vxml_url) { /* If there is a VXML URL append it to the SIP URL */ - ret = snprintf(to, sizeof(to), "<%s>;%s", p->uri, p->options->vxml_url); + ret = ast_str_set(&to, 0, "<%s>;%s", p->uri, p->options->vxml_url); } else { - ret = snprintf(to, sizeof(to), "<%s>", p->uri); + ret = ast_str_set(&to, 0, "<%s>", p->uri); } } - if (ret < 0 || ret >= sizeof(to)) { /* a return value of size or more means that the output was truncated */ + if (ret == AST_DYNSTR_BUILD_FAILED) { /* We don't have an escape path from here... */ ast_log(LOG_ERROR, "The To header was truncated in call '%s'. This call setup will fail.\n", p->callid); + /* Make sure that the field contains something non-broken. + See https://issues.asterisk.org/jira/browse/ASTERISK-26069 + */ + ast_str_set(&to, 3, "<>"); } init_req(req, sipmethod, p->uri); @@ -14360,8 +14347,8 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho */ add_route(req, &p->route, 0); - add_header(req, "From", from); - add_header(req, "To", to); + add_header(req, "From", ast_str_buffer(from)); + add_header(req, "To", ast_str_buffer(to)); ast_string_field_set(p, exten, l); build_contact(p, req, 0); add_header(req, "Contact", p->our_contact); @@ -14370,6 +14357,10 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho if (!ast_strlen_zero(global_useragent)) { add_header(req, "User-Agent", global_useragent); } + + ast_free(from); + ast_free(to); + ast_free(invite); } /*! \brief Add "Diversion" header to outgoing message @@ -15790,11 +15781,12 @@ static void start_register_timeout(struct sip_registry *reg) static const char *sip_sanitized_host(const char *host) { - struct ast_sockaddr addr = { { 0, 0, }, }; + struct ast_sockaddr addr; /* peer/sip_pvt->tohost and sip_registry->hostname should never have a port * in them, so we use PARSE_PORT_FORBID here. If this lookup fails, we return * the original host which is most likely a host name and not an IP. */ + memset(&addr, 0, sizeof(addr)); if (!ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID)) { return host; } diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c index db4720d1a..37281bb48 100644 --- a/channels/chan_unistim.c +++ b/channels/chan_unistim.c @@ -567,8 +567,10 @@ static const unsigned char packet_send_stream_based_tone_off[] = { 0x16, 0x05, 0x1c, 0x00, 0x00 }; static const unsigned char packet_send_mute[] = { 0x16, 0x05, 0x04, 0x00, 0x00 }; +#ifdef NOT_USED static const unsigned char packet_send_CloseAudioStreamRX[] = { 0x16, 0x05, 0x31, 0x00, 0xff }; static const unsigned char packet_send_CloseAudioStreamTX[] = { 0x16, 0x05, 0x31, 0xff, 0x00 }; +#endif static const unsigned char packet_send_stream_based_tone_on[] = { 0x16, 0x06, 0x1b, 0x00, 0x00, 0x05 }; static const unsigned char packet_send_stream_based_tone_single_freq[] = @@ -1021,7 +1023,7 @@ static int get_to_address(int fd, struct sockaddr_in *toAddr) memcpy(&toAddr->sin_addr, &ip_msg.address, sizeof(struct in_addr)); return err; #else - memcpy(&toAddr, &public_ip, sizeof(&toAddr)); + memcpy(toAddr, &public_ip, sizeof(*toAddr)); return 0; #endif } diff --git a/configs/basic-pbx/modules.conf b/configs/basic-pbx/modules.conf index 356153713..7b60125b7 100644 --- a/configs/basic-pbx/modules.conf +++ b/configs/basic-pbx/modules.conf @@ -60,11 +60,9 @@ load = func_strings.so ; Core/PBX load = pbx_config.so -load = pbx_functions.so ; Resources -load = res_hep_pjsip.so load = res_musiconhold.so load = res_pjproject.so load = res_pjsip_acl.so @@ -78,7 +76,6 @@ load = res_pjsip_endpoint_identifier_ip.so load = res_pjsip_endpoint_identifier_user.so load = res_pjsip_exten_state.so load = res_pjsip_header_funcs.so -load = res_pjsip_log_forwarder.so load = res_pjsip_logger.so load = res_pjsip_messaging.so load = res_pjsip_multihomed.so @@ -91,7 +88,6 @@ load = res_pjsip_outbound_authenticator_digest.so load = res_pjsip_outbound_publish.so load = res_pjsip_outbound_registration.so load = res_pjsip_path.so -load = res_pjsip_phoneprov_provider.so load = res_pjsip_pidf_body_generator.so load = res_pjsip_pidf_digium_body_supplement.so load = res_pjsip_pidf_eyebeam_body_supplement.so diff --git a/configs/samples/hep.conf.sample b/configs/samples/hep.conf.sample index 6e409d151..e1cd52ebb 100644 --- a/configs/samples/hep.conf.sample +++ b/configs/samples/hep.conf.sample @@ -4,7 +4,7 @@ ; All settings are currently set in the general section. [general] -enabled = yes ; Enable/disable forwarding of packets to a +enabled = no ; Enable/disable forwarding of packets to a ; HEP server. Default is "yes". capture_address = 192.168.1.1:9061 ; The address of the HEP capture server. capture_password = foo ; If specified, the authorization passsword diff --git a/configs/samples/res_odbc.conf.sample b/configs/samples/res_odbc.conf.sample index 66659ae42..a21e96d07 100644 --- a/configs/samples/res_odbc.conf.sample +++ b/configs/samples/res_odbc.conf.sample @@ -51,6 +51,11 @@ pre-connect => yes ; that we should attempt? ;limit => 5 ; +; The maximum number of connections to have open at any given time. +; This defaults to 1 and it is highly recommended to only set this higher +; if using a version of UnixODBC greater than 2.3.1. +;max_connections => 20 +; ; When the channel is destroyed, should any uncommitted open transactions ; automatically be committed? ;forcecommit => no @@ -915,6 +915,10 @@ PBX_POPT POPT_DIR POPT_INCLUDE POPT_LIB +PBX_PJSIP_EVSUB_GRP_LOCK +PJSIP_EVSUB_GRP_LOCK_DIR +PJSIP_EVSUB_GRP_LOCK_INCLUDE +PJSIP_EVSUB_GRP_LOCK_LIB PBX_PJSIP_TLS_TRANSPORT_PROTO PJSIP_TLS_TRANSPORT_PROTO_DIR PJSIP_TLS_TRANSPORT_PROTO_INCLUDE @@ -10563,6 +10567,18 @@ PBX_PJSIP_TLS_TRANSPORT_PROTO=0 +PJSIP_EVSUB_GRP_LOCK_DESCRIP="PJSIP EVSUB Group Lock support" +PJSIP_EVSUB_GRP_LOCK_OPTION=pjsip +PJSIP_EVSUB_GRP_LOCK_DIR=${PJPROJECT_DIR} + +PBX_PJSIP_EVSUB_GRP_LOCK=0 + + + + + + + POPT_DESCRIP="popt" POPT_OPTION="popt" @@ -13612,7 +13628,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -13658,7 +13674,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -13682,7 +13698,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -13727,7 +13743,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -13751,7 +13767,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -15148,46 +15164,6 @@ _ACEOF rm -f conftest* -if ${ac_cv_func_setvbuf_reversed+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_cv_func_setvbuf_reversed=no -fi - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking return type of signal handlers" >&5 -$as_echo_n "checking return type of signal handlers... " >&6; } -if ${ac_cv_type_signal+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <sys/types.h> -#include <signal.h> - -int -main () -{ -return *(signal (0, 0)) (0) == 1; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_type_signal=int -else - ac_cv_type_signal=void -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_signal" >&5 -$as_echo "$ac_cv_type_signal" >&6; } - -cat >>confdefs.h <<_ACEOF -#define RETSIGTYPE $ac_cv_type_signal -_ACEOF - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether lstat correctly handles trailing slash" >&5 $as_echo_n "checking whether lstat correctly handles trailing slash... " >&6; } if ${ac_cv_func_lstat_dereferences_slashed_symlink+:} false; then : @@ -24463,6 +24439,9 @@ rm -f conftest* $as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_PROTO 1" >>confdefs.h +$as_echo "#define HAVE_PJSIP_EVSUB_GRP_LOCK 1" >>confdefs.h + + else if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then @@ -25178,6 +25157,111 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext LIBS="${saved_libs}" CPPFLAGS="${saved_cppflags}" + + +if test "x${PBX_PJSIP_EVSUB_GRP_LOCK}" != "x1" -a "${USE_PJSIP_EVSUB_GRP_LOCK}" != "no"; then + pbxlibdir="" + # if --with-PJSIP_EVSUB_GRP_LOCK=DIR has been specified, use it. + if test "x${PJSIP_EVSUB_GRP_LOCK_DIR}" != "x"; then + if test -d ${PJSIP_EVSUB_GRP_LOCK_DIR}/lib; then + pbxlibdir="-L${PJSIP_EVSUB_GRP_LOCK_DIR}/lib" + else + pbxlibdir="-L${PJSIP_EVSUB_GRP_LOCK_DIR}" + fi + fi + pbxfuncname="pjsip_evsub_add_ref" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_PJSIP_EVSUB_GRP_LOCK_FOUND=yes + else + ast_ext_lib_check_save_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS" + as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5 +$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ${pbxfuncname} (); +int +main () +{ +return ${pbxfuncname} (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + AST_PJSIP_EVSUB_GRP_LOCK_FOUND=yes +else + AST_PJSIP_EVSUB_GRP_LOCK_FOUND=no +fi + + CFLAGS="${ast_ext_lib_check_save_CFLAGS}" + fi + + # now check for the header. + if test "${AST_PJSIP_EVSUB_GRP_LOCK_FOUND}" = "yes"; then + PJSIP_EVSUB_GRP_LOCK_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB" + # if --with-PJSIP_EVSUB_GRP_LOCK=DIR has been specified, use it. + if test "x${PJSIP_EVSUB_GRP_LOCK_DIR}" != "x"; then + PJSIP_EVSUB_GRP_LOCK_INCLUDE="-I${PJSIP_EVSUB_GRP_LOCK_DIR}/include" + fi + PJSIP_EVSUB_GRP_LOCK_INCLUDE="${PJSIP_EVSUB_GRP_LOCK_INCLUDE} $PJPROJECT_CFLAGS" + if test "xpjsip.h" = "x" ; then # no header, assume found + PJSIP_EVSUB_GRP_LOCK_HEADER_FOUND="1" + else # check for the header + ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${PJSIP_EVSUB_GRP_LOCK_INCLUDE}" + ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default" +if test "x$ac_cv_header_pjsip_h" = xyes; then : + PJSIP_EVSUB_GRP_LOCK_HEADER_FOUND=1 +else + PJSIP_EVSUB_GRP_LOCK_HEADER_FOUND=0 +fi + + + CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" + fi + if test "x${PJSIP_EVSUB_GRP_LOCK_HEADER_FOUND}" = "x0" ; then + PJSIP_EVSUB_GRP_LOCK_LIB="" + PJSIP_EVSUB_GRP_LOCK_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + PJSIP_EVSUB_GRP_LOCK_LIB="" + fi + PBX_PJSIP_EVSUB_GRP_LOCK=1 + cat >>confdefs.h <<_ACEOF +#define HAVE_PJSIP_EVSUB_GRP_LOCK 1 +_ACEOF + + fi + fi +fi + + fi fi diff --git a/configure.ac b/configure.ac index f2e42ba1f..9544060cd 100644 --- a/configure.ac +++ b/configure.ac @@ -487,6 +487,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_GET_DEST_INFO], [pjsip_get_dest_info support], AST_EXT_LIB_SETUP_OPTIONAL([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_from_files2 support], [PJPROJECT], [pjsip]) AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EXTERNAL_RESOLVER], [PJSIP External Resolver Support], [PJPROJECT], [pjsip]) AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_PROTO], [PJSIP TLS Transport proto field support], [PJPROJECT], [pjsip]) +AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_GRP_LOCK], [PJSIP EVSUB Group Lock support], [PJPROJECT], [pjsip]) AST_EXT_LIB_SETUP([POPT], [popt], [popt]) AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio]) @@ -682,8 +683,6 @@ AC_PROG_GCC_TRADITIONAL AC_FUNC_MEMCMP AC_FUNC_MMAP AC_FUNC_SELECT_ARGTYPES -AC_FUNC_SETVBUF_REVERSED -AC_TYPE_SIGNAL AC_FUNC_STAT AC_FUNC_STRCOLL AC_FUNC_STRFTIME @@ -1230,7 +1229,7 @@ AC_SUBST(AST_NATIVE_ARCH) dnl Check to see if rpath should be set in LDFLAGS AC_ARG_ENABLE(rpath, - [AC_HELP_STRING([--disable-rpath], + [AS_HELP_STRING([--disable-rpath], [Disables rpath linker option checking])], [case "${enableval}" in y|ye|yes) check_rpath=yes ;; @@ -2194,6 +2193,8 @@ if test "$USE_PJPROJECT" != "no" ; then AST_C_COMPILE_CHECK([PJSIP_TLS_TRANSPORT_PROTO], [struct pjsip_tls_setting setting; int proto; proto = setting.proto;], [pjsip.h]) LIBS="${saved_libs}" CPPFLAGS="${saved_cppflags}" + + AST_EXT_LIB_CHECK([PJSIP_EVSUB_GRP_LOCK], [pjsip], [pjsip_evsub_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) fi fi diff --git a/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py index c25fc7233..0919370ba 100644 --- a/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py +++ b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py @@ -16,10 +16,8 @@ import sqlalchemy as sa def upgrade(): op.add_column('ps_contacts', sa.Column('reg_server', sa.String(20))) - op.drop_constraint(UniqueConstraint('id'), 'ps_contacts', type_='unique') op.create_unique_constraint('ps_contacts_uq', 'ps_contacts', ['id','reg_server']) def downgrade(): op.drop_constraint('ps_contacts_uq', 'ps_contacts', type_='unique') op.drop_column('ps_contacts', 'reg_server') - op.create_unique_constraint(None, 'ps_contacts', 'id') diff --git a/funcs/func_env.c b/funcs/func_env.c index 3c260a2fb..072714f19 100644 --- a/funcs/func_env.c +++ b/funcs/func_env.c @@ -624,7 +624,7 @@ static int file_read(struct ast_channel *chan, const char *cmd, char *data, stru ast_log(LOG_ERROR, "Cannot seek to offset %" PRId64 ": %s\n", i, strerror(errno)); } end = fread(fbuf, 1, sizeof(fbuf), ff); - for (pos = (end < sizeof(fbuf) ? fbuf + end - 1 : fbuf + sizeof(fbuf) - 1); pos > fbuf - 1; pos--) { + for (pos = (end < sizeof(fbuf) ? fbuf + end - 1 : fbuf + sizeof(fbuf) - 1); pos >= fbuf; pos--) { LINE_COUNTER(pos, format, count); if (length < 0 && count * -1 == length) { @@ -1024,7 +1024,7 @@ static int file_write(struct ast_channel *chan, const char *cmd, char *data, con fclose(ff); return -1; } - for (pos = fbuf + sizeof(fbuf) - 1; pos > fbuf - 1; pos--) { + for (pos = fbuf + sizeof(fbuf) - 1; pos >= fbuf; pos--) { LINE_COUNTER(pos, newline_format, count); if (length < 0 && count * -1 == length) { diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index a01131cc3..64580205c 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -587,6 +587,9 @@ /* Define if your system has pjsip_dlg_create_uas_and_inc_lock declared. */ #undef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK +/* Define if your system has PJSIP_EVSUB_GRP_LOCK */ +#undef HAVE_PJSIP_EVSUB_GRP_LOCK + /* Define if your system has pjsip_endpt_set_ext_resolver declared. */ #undef HAVE_PJSIP_EXTERNAL_RESOLVER @@ -1249,9 +1252,6 @@ /* Define if your system needs braces around PTHREAD_ONCE_INIT */ #undef PTHREAD_ONCE_INIT_NEEDS_BRACES -/* Define as the return type of signal handlers (`int' or `void'). */ -#undef RETSIGTYPE - /* Define to the type of arg 1 for `select'. */ #undef SELECT_TYPE_ARG1 diff --git a/include/asterisk/compat.h b/include/asterisk/compat.h index c9c99c150..c7bc5b093 100644 --- a/include/asterisk/compat.h +++ b/include/asterisk/compat.h @@ -68,7 +68,7 @@ #endif #ifndef AST_POLL_COMPAT -#include <sys/poll.h> +#include <poll.h> #else #include "asterisk/poll-compat.h" #endif diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h index 30c042176..30d0c9142 100644 --- a/include/asterisk/config_options.h +++ b/include/asterisk/config_options.h @@ -445,6 +445,28 @@ enum aco_option_type { * {endcode} */ OPT_UINT_T, + + /*! \brief Type for default option handler for bools (ast_true/ast_false) + * \note aco_option_register flags: + * non-zero : process via ast_true + * 0 : process via ast_false + * aco_option_register varargs: + * FLDSET macro with the field of type int. It is important to note that the field + * cannot be a bitfield. If bitfields are required, they must be set via a custom handler. + * + * This is exactly the same as OPT_BOOL_T. The only difference is that when + * translated to a string, OPT_BOOL_T becomes "true" or "false"; OPT_YESNO_T becomes + * "yes" or "no". + * + * Example: + * {code} + * struct test_item { + * int enabled; + * }; + * aco_option_register(&cfg_info, "enabled", ACO_EXACT, my_types, "no", OPT_YESNO_T, 1, FLDSET(struct test_item, enabled)); + * {endcode} + */ + OPT_YESNO_T, }; /*! \brief A callback function for handling a particular option diff --git a/include/asterisk/multicast_rtp.h b/include/asterisk/multicast_rtp.h new file mode 100644 index 000000000..c286c1f96 --- /dev/null +++ b/include/asterisk/multicast_rtp.h @@ -0,0 +1,58 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Digium, Inc. + * + * Mark Michelson <mmichelson@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. + */ + +#ifndef MULTICAST_RTP_H_ +#define MULTICAST_RTP_H_ +struct ast_multicast_rtp_options; + +/*! + * \brief Create multicast RTP options. + * + * These are passed to the multicast RTP engine on its creation. + * + * \param type The type of multicast RTP, either "basic" or "linksys" + * \param options Miscellaneous options + * \retval NULL Failure + * \retval non-NULL success + */ +struct ast_multicast_rtp_options *ast_multicast_rtp_create_options(const char *type, + const char *options); + +/*! + * \brief Free multicast RTP options + * + * This function is NULL-tolerant + * + * \param mcast_options Options to free + */ +void ast_multicast_rtp_free_options(struct ast_multicast_rtp_options *mcast_options); + +/*! + * \brief Get format specified in multicast options + * + * Multicast options allow for a format to be selected. + * This function accesses the selected format and creates + * an ast_format structure for it. + * + * \param mcast_options The options where a codec was specified + * \retval NULL No format specified in the options + * \revval non-NULL The format to use for communication + */ +struct ast_format *ast_multicast_rtp_options_get_format(struct ast_multicast_rtp_options *mcast_options); + +#endif /* MULTICAST_RTP_H_ */ diff --git a/include/asterisk/poll-compat.h b/include/asterisk/poll-compat.h index cbb610925..72ac2c3e2 100644 --- a/include/asterisk/poll-compat.h +++ b/include/asterisk/poll-compat.h @@ -83,7 +83,7 @@ #ifndef AST_POLL_COMPAT -#include <sys/poll.h> +#include <poll.h> #define ast_poll(a, b, c) poll(a, b, c) diff --git a/include/asterisk/res_hep.h b/include/asterisk/res_hep.h index bd0129eea..cfd213ad7 100644 --- a/include/asterisk/res_hep.h +++ b/include/asterisk/res_hep.h @@ -118,6 +118,14 @@ int hepv3_send_packet(struct hepv3_capture_info *capture_info); */ enum hep_uuid_type hepv3_get_uuid_type(void); +/*! + * \brief Return whether or not we're currently loaded and active + * + * \retval 0 The module is not loaded + * \retval 1 The module is loaded + */ +int hepv3_is_loaded(void); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 4319dbdb6..5b830ea2e 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -1348,6 +1348,17 @@ struct ast_taskprocessor *ast_sip_create_serializer_group(struct ast_serializer_ struct ast_taskprocessor *ast_sip_create_serializer_group_named(const char *name, struct ast_serializer_shutdown_group *shutdown_group); /*! + * \brief Determine the distributor serializer for the SIP message. + * \since 13.10.0 + * + * \param rdata The incoming message. + * + * \retval Calculated distributor serializer on success. + * \retval NULL on error. + */ +struct ast_taskprocessor *ast_sip_get_distributor_serializer(pjsip_rx_data *rdata); + +/*! * \brief Set a serializer on a SIP dialog so requests and responses are automatically serialized * * Passing a NULL serializer is a way to remove a serializer from a dialog. diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index 75d37ac3d..e4c54a173 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -406,9 +406,10 @@ struct ast_sip_channel_pvt *ast_sip_channel_pvt_alloc(void *pvt, struct ast_sip_ * \param endpoint The endpoint that this session communicates with * \param contact The contact associated with this session * \param inv_session The PJSIP INVITE session data + * \param rdata INVITE request received (NULL if for outgoing allocation) */ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, - struct ast_sip_contact *contact, pjsip_inv_session *inv); + struct ast_sip_contact *contact, pjsip_inv_session *inv, pjsip_rx_data *rdata); /*! * \brief Request and wait for the session serializer to be suspended. diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h index 5e947257b..0cb434766 100644 --- a/include/asterisk/sorcery.h +++ b/include/asterisk/sorcery.h @@ -692,6 +692,20 @@ int __ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, __ast_sorcery_object_register((sorcery), (type), 1, 1, (alloc), (transform), (apply)) /*! + * \brief Set the high and low alert water marks of the sorcery object type. + * \since 13.10.0 + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param low_water New queue low water mark. (-1 to set as 90% of high_water) + * \param high_water New queue high water mark. + * + * \retval 0 on success. + * \retval -1 on error (water marks not changed). + */ +int ast_sorcery_object_set_congestion_levels(struct ast_sorcery *sorcery, const char *type, long low_water, long high_water); + +/*! * \brief Set the copy handler for an object type * * \param sorcery Pointer to a sorcery structure diff --git a/include/asterisk/stasis.h b/include/asterisk/stasis.h index 4fc295bc4..de44206b6 100644 --- a/include/asterisk/stasis.h +++ b/include/asterisk/stasis.h @@ -601,6 +601,20 @@ struct stasis_subscription *stasis_unsubscribe( struct stasis_subscription *subscription); /*! + * \brief Set the high and low alert water marks of the stasis subscription. + * \since 13.10.0 + * + * \param subscription Pointer to a stasis subscription + * \param low_water New queue low water mark. (-1 to set as 90% of high_water) + * \param high_water New queue high water mark. + * + * \retval 0 on success. + * \retval -1 on error (water marks not changed). + */ +int stasis_subscription_set_congestion_limits(struct stasis_subscription *subscription, + long low_water, long high_water); + +/*! * \brief Block until the last message is processed on a subscription. * * This function will not return until the \a subscription's callback for the diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 90ef82ebf..8ceeffba3 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -746,6 +746,15 @@ int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge, struct stasis_app_control *control); /*! + * \brief remove channel from list of ARI playback channels for bridges. + * + * \param bridge_id The unique ID of the bridge the playback channel is in. + * \param control The app control structure for the playback channel + */ +void stasis_app_bridge_playback_channel_remove(char *bridge_id, + struct stasis_app_control *control); + +/*! * \brief Result codes used when adding/removing channels to/from bridges. */ enum stasis_app_control_channel_result { diff --git a/include/asterisk/stasis_message_router.h b/include/asterisk/stasis_message_router.h index 89657a5ee..50270a788 100644 --- a/include/asterisk/stasis_message_router.h +++ b/include/asterisk/stasis_message_router.h @@ -127,6 +127,20 @@ void stasis_message_router_publish_sync(struct stasis_message_router *router, struct stasis_message *message); /*! + * \brief Set the high and low alert water marks of the stasis message router. + * \since 13.10.0 + * + * \param router Pointer to a stasis message router + * \param low_water New queue low water mark. (-1 to set as 90% of high_water) + * \param high_water New queue high water mark. + * + * \retval 0 on success. + * \retval -1 on error (water marks not changed). + */ +int stasis_message_router_set_congestion_limits(struct stasis_message_router *router, + long low_water, long high_water); + +/*! * \brief Add a route to a message router. * * A particular \a message_type may have at most one route per \a router. If diff --git a/include/asterisk/taskprocessor.h b/include/asterisk/taskprocessor.h index af3ce747f..e51122269 100644 --- a/include/asterisk/taskprocessor.h +++ b/include/asterisk/taskprocessor.h @@ -59,6 +59,7 @@ struct ast_taskprocessor; /*! \brief Suggested maximum taskprocessor name length (less null terminator). */ #define AST_TASKPROCESSOR_MAX_NAME 45 +/*! Default taskprocessor high water level alert trigger */ #define AST_TASKPROCESSOR_HIGH_WATER_LEVEL 500 /*! @@ -297,4 +298,26 @@ const char *ast_taskprocessor_name(struct ast_taskprocessor *tps); */ long ast_taskprocessor_size(struct ast_taskprocessor *tps); +/*! + * \brief Get the current taskprocessor high water alert count. + * \since 13.10.0 + * + * \retval 0 if no taskprocessors are in high water alert. + * \retval non-zero if some task processors are in high water alert. + */ +unsigned int ast_taskprocessor_alert_get(void); + +/*! + * \brief Set the high and low alert water marks of the given taskprocessor queue. + * \since 13.10.0 + * + * \param tps Taskprocessor to update queue water marks. + * \param low_water New queue low water mark. (-1 to set as 90% of high_water) + * \param high_water New queue high water mark. + * + * \retval 0 on success. + * \retval -1 on error (water marks not changed). + */ +int ast_taskprocessor_alert_set_levels(struct ast_taskprocessor *tps, long low_water, long high_water); + #endif /* __AST_TASKPROCESSOR_H__ */ diff --git a/main/ast_expr2.c b/main/ast_expr2.c index 798e3d3ce..781abd95a 100644 --- a/main/ast_expr2.c +++ b/main/ast_expr2.c @@ -93,6 +93,7 @@ #include "asterisk.h" +#include <sys/cdefs.h> #include <sys/types.h> #include <stdio.h> @@ -3667,13 +3668,20 @@ op_tildetilde (struct val *a, struct val *b) /* strip double quotes from both -- */ strip_quotes(a); strip_quotes(b); - + vs = malloc(strlen(a->u.s)+strlen(b->u.s)+1); + if (vs == NULL) { + ast_log(LOG_WARNING, "malloc() failed\n"); + return NULL; + } + strcpy(vs,a->u.s); strcat(vs,b->u.s); v = make_str(vs); + free(vs); + /* free arguments */ free_value(a); free_value(b); diff --git a/main/ast_expr2.y b/main/ast_expr2.y index 83d3effe3..913bc2662 100644 --- a/main/ast_expr2.y +++ b/main/ast_expr2.y @@ -14,6 +14,7 @@ #include "asterisk.h" +#include <sys/cdefs.h> #include <sys/types.h> #include <stdio.h> @@ -1660,13 +1661,20 @@ op_tildetilde (struct val *a, struct val *b) /* strip double quotes from both -- */ strip_quotes(a); strip_quotes(b); - + vs = malloc(strlen(a->u.s)+strlen(b->u.s)+1); + if (vs == NULL) { + ast_log(LOG_WARNING, "malloc() failed\n"); + return NULL; + } + strcpy(vs,a->u.s); strcat(vs,b->u.s); v = make_str(vs); + free(vs); + /* free arguments */ free_value(a); free_value(b); diff --git a/main/asterisk.c b/main/asterisk.c index ea998d492..164e659ba 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -606,6 +606,7 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c char buf[BUFSIZ]; struct ast_tm tm; char eid_str[128]; + struct rlimit limits; switch (cmd) { case CLI_INIT: @@ -627,10 +628,17 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c ast_cli(a->fd, " Maximum calls: %d (Current %d)\n", ast_option_maxcalls, ast_active_channels()); else ast_cli(a->fd, " Maximum calls: Not set\n"); - if (ast_option_maxfiles) - ast_cli(a->fd, " Maximum open file handles: %d\n", ast_option_maxfiles); - else - ast_cli(a->fd, " Maximum open file handles: Not set\n"); + + if (getrlimit(RLIMIT_NOFILE, &limits)) { + ast_cli(a->fd, " Maximum open file handles: Error because of %s\n", strerror(errno)); + } else if (limits.rlim_cur == RLIM_INFINITY) { + ast_cli(a->fd, " Maximum open file handles: Unlimited\n"); + } else if (limits.rlim_cur < ast_option_maxfiles) { + ast_cli(a->fd, " Maximum open file handles: %d (is) %d (requested)\n", (int) limits.rlim_cur, ast_option_maxfiles); + } else { + ast_cli(a->fd, " Maximum open file handles: %d\n", (int) limits.rlim_cur); + } + ast_cli(a->fd, " Root console verbosity: %d\n", option_verbose); ast_cli(a->fd, " Current console verbosity: %d\n", ast_verb_console_get()); ast_cli(a->fd, " Debug level: %d\n", option_debug); diff --git a/main/astfd.c b/main/astfd.c index d2cb73a6b..a96471d60 100644 --- a/main/astfd.c +++ b/main/astfd.c @@ -271,7 +271,7 @@ static char *handle_show_fd(struct ast_cli_entry *e, int cmd, struct ast_cli_arg case CLI_GENERATE: return NULL; } - getrlimit(RLIMIT_FSIZE, &rl); + getrlimit(RLIMIT_NOFILE, &rl); if (rl.rlim_cur == RLIM_INFINITY || rl.rlim_max == RLIM_INFINITY) { ast_copy_string(line, "unlimited", sizeof(line)); } else { diff --git a/main/bridge_channel.c b/main/bridge_channel.c index 4baae3cc5..543988dde 100644 --- a/main/bridge_channel.c +++ b/main/bridge_channel.c @@ -2162,9 +2162,10 @@ int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel, ast_bridge_publish_enter(bridge, bridge_channel->chan, swap ? swap->chan : NULL); - /* Clear any BLINDTRANSFER and ATTENDEDTRANSFER since the transfer has completed. */ + /* Clear any BLINDTRANSFER,ATTENDEDTRANSFER and FORWARDERNAME since the transfer has completed. */ pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", NULL); pbx_builtin_setvar_helper(bridge_channel->chan, "ATTENDEDTRANSFER", NULL); + pbx_builtin_setvar_helper(bridge_channel->chan, "FORWARDERNAME", NULL); /* Wake up the bridge channel thread to reevaluate any interval timers. */ ast_queue_frame(bridge_channel->chan, &ast_null_frame); diff --git a/main/cdr.c b/main/cdr.c index 7795a65fd..ab6530ed3 100644 --- a/main/cdr.c +++ b/main/cdr.c @@ -71,6 +71,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_bridges.h" #include "asterisk/stasis_message_router.h" #include "asterisk/astobj2.h" +#include "asterisk/taskprocessor.h" /*** DOCUMENTATION <configInfo name="cdr" language="en_US"> @@ -4184,6 +4185,8 @@ int ast_cdr_engine_init(void) if (!stasis_router) { return -1; } + stasis_message_router_set_congestion_limits(stasis_router, -1, + 10 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL); if (STASIS_MESSAGE_TYPE_INIT(cdr_sync_message_type)) { return -1; diff --git a/main/cel.c b/main/cel.c index d9fcc5f6b..4abaac7c8 100644 --- a/main/cel.c +++ b/main/cel.c @@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/parking.h" #include "asterisk/pickup.h" #include "asterisk/core_local.h" +#include "asterisk/taskprocessor.h" /*** DOCUMENTATION <configInfo name="cel" language="en_US"> @@ -170,6 +171,13 @@ struct cel_linkedid { /*! Container of channel references to a linkedid for CEL purposes. */ static AO2_GLOBAL_OBJ_STATIC(cel_linkedids); +struct cel_dialstatus { + /*! Uniqueid of the channel */ + char uniqueid[AST_MAX_UNIQUEID]; + /*! The dial status */ + char dialstatus[0]; +}; + /*! \brief Destructor for cel_config */ static void cel_general_config_dtor(void *obj) { @@ -372,20 +380,10 @@ static int cel_backend_cmp(void *obj, void *arg, int flags) return CMP_MATCH; } -static const char *get_caller_uniqueid(struct ast_multi_channel_blob *blob) -{ - struct ast_channel_snapshot *caller = ast_multi_channel_blob_get_channel(blob, "caller"); - if (!caller) { - return NULL; - } - - return caller->uniqueid; -} - /*! \brief Hashing function for dialstatus container */ static int dialstatus_hash(const void *obj, int flags) { - struct ast_multi_channel_blob *blob; + const struct cel_dialstatus *dialstatus; const char *key; switch (flags & OBJ_SEARCH_MASK) { @@ -393,8 +391,8 @@ static int dialstatus_hash(const void *obj, int flags) key = obj; break; case OBJ_SEARCH_OBJECT: - blob = (void *) obj; - key = get_caller_uniqueid(blob); + dialstatus = obj; + key = dialstatus->uniqueid; break; default: /* Hash can only work on something with a full key. */ @@ -407,24 +405,24 @@ static int dialstatus_hash(const void *obj, int flags) /*! \brief Comparator function for dialstatus container */ static int dialstatus_cmp(void *obj, void *arg, int flags) { - struct ast_multi_channel_blob *object_left = obj; - struct ast_multi_channel_blob *object_right = arg; + struct cel_dialstatus *object_left = obj; + struct cel_dialstatus *object_right = arg; const char *right_key = arg; int cmp; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: - right_key = get_caller_uniqueid(object_right); + right_key = object_right->uniqueid; /* Fall through */ case OBJ_SEARCH_KEY: - cmp = strcmp(get_caller_uniqueid(object_left), right_key); + cmp = strcmp(object_left->uniqueid, right_key); break; case OBJ_SEARCH_PARTIAL_KEY: /* * We could also use a partial key struct containing a length * so strlen() does not get called for every comparison instead. */ - cmp = strncmp(get_caller_uniqueid(object_left), right_key, strlen(right_key)); + cmp = strncmp(object_left->uniqueid, right_key, strlen(right_key)); break; default: /* @@ -958,16 +956,16 @@ typedef void (*cel_channel_snapshot_monitor)( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot); -static struct ast_multi_channel_blob *get_dialstatus_blob(const char *uniqueid) +static struct cel_dialstatus *get_dialstatus(const char *uniqueid) { struct ao2_container *dial_statuses = ao2_global_obj_ref(cel_dialstatus_store); - struct ast_multi_channel_blob *blob = NULL; + struct cel_dialstatus *dialstatus = NULL; if (dial_statuses) { - blob = ao2_find(dial_statuses, uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK); + dialstatus = ao2_find(dial_statuses, uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK); ao2_ref(dial_statuses, -1); } - return blob; + return dialstatus; } static const char *get_blob_variable(struct ast_multi_channel_blob *blob, const char *varname) @@ -1010,19 +1008,15 @@ static void cel_channel_state_change( if (!was_hungup && is_hungup) { struct ast_json *extra; - struct ast_multi_channel_blob *blob = get_dialstatus_blob(new_snapshot->uniqueid); - const char *dialstatus = ""; + struct cel_dialstatus *dialstatus = get_dialstatus(new_snapshot->uniqueid); - if (blob && !ast_strlen_zero(get_blob_variable(blob, "dialstatus"))) { - dialstatus = get_blob_variable(blob, "dialstatus"); - } extra = ast_json_pack("{s: i, s: s, s: s}", "hangupcause", new_snapshot->hangupcause, "hangupsource", new_snapshot->hangupsource, - "dialstatus", dialstatus); + "dialstatus", dialstatus ? dialstatus->dialstatus : ""); cel_report_event(new_snapshot, AST_CEL_HANGUP, NULL, extra, NULL); ast_json_unref(extra); - ao2_cleanup(blob); + ao2_cleanup(dialstatus); return; } @@ -1254,16 +1248,48 @@ static void cel_parking_cb( } } -static void save_dialstatus(struct ast_multi_channel_blob *blob) +static void save_dialstatus(struct ast_multi_channel_blob *blob, struct ast_channel_snapshot *snapshot) { struct ao2_container *dial_statuses = ao2_global_obj_ref(cel_dialstatus_store); + const char *dialstatus_string = get_blob_variable(blob, "dialstatus"); + struct cel_dialstatus *dialstatus; + size_t dialstatus_string_len; - ast_assert(blob != NULL); + if (!dial_statuses || ast_strlen_zero(dialstatus_string)) { + ao2_cleanup(dial_statuses); + return; + } - if (dial_statuses) { - ao2_link(dial_statuses, blob); + dialstatus = ao2_find(dial_statuses, snapshot->uniqueid, OBJ_SEARCH_KEY); + if (dialstatus) { + if (!strcasecmp(dialstatus_string, "ANSWER") && strcasecmp(dialstatus->dialstatus, "ANSWER")) { + /* In the case of an answer after we already have a dial status we give + * priority to the answer since the call was, well, answered. In the case of + * failure dial status results we simply let the first failure be the status. + */ + ao2_unlink(dial_statuses, dialstatus); + ao2_ref(dialstatus, -1); + } else { + ao2_ref(dialstatus, -1); + ao2_ref(dial_statuses, -1); + return; + } + } + + dialstatus_string_len = strlen(dialstatus_string) + 1; + dialstatus = ao2_alloc_options(sizeof(*dialstatus) + dialstatus_string_len, NULL, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!dialstatus) { ao2_ref(dial_statuses, -1); + return; } + + ast_copy_string(dialstatus->uniqueid, snapshot->uniqueid, sizeof(dialstatus->uniqueid)); + ast_copy_string(dialstatus->dialstatus, dialstatus_string, dialstatus_string_len); + + ao2_link(dial_statuses, dialstatus); + ao2_ref(dialstatus, -1); + ao2_ref(dial_statuses, -1); } static int is_valid_dialstatus(struct ast_multi_channel_blob *blob) @@ -1299,32 +1325,25 @@ static void cel_dial_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_multi_channel_blob *blob = stasis_message_data(message); + struct ast_channel_snapshot *snapshot; - if (cel_filter_channel_snapshot(ast_multi_channel_blob_get_channel(blob, "caller"))) { - return; - } - - if (!get_caller_uniqueid(blob)) { + snapshot = ast_multi_channel_blob_get_channel(blob, "caller"); + if (!snapshot || cel_filter_channel_snapshot(snapshot)) { return; } if (!ast_strlen_zero(get_blob_variable(blob, "forward"))) { - struct ast_channel_snapshot *caller = ast_multi_channel_blob_get_channel(blob, "caller"); struct ast_json *extra; - if (!caller) { - return; - } - extra = ast_json_pack("{s: s}", "forward", get_blob_variable(blob, "forward")); if (extra) { - cel_report_event(caller, AST_CEL_FORWARD, NULL, extra, NULL); + cel_report_event(snapshot, AST_CEL_FORWARD, NULL, extra, NULL); ast_json_unref(extra); } } if (is_valid_dialstatus(blob)) { - save_dialstatus(blob); + save_dialstatus(blob, snapshot); } } @@ -1575,6 +1594,8 @@ static int create_routes(void) if (!cel_state_router) { return -1; } + stasis_message_router_set_congestion_limits(cel_state_router, -1, + 6 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL); ret |= stasis_message_router_add(cel_state_router, stasis_cache_update_type(), diff --git a/main/channel.c b/main/channel.c index 4ed1f8b8a..327ec64a6 100644 --- a/main/channel.c +++ b/main/channel.c @@ -5663,6 +5663,7 @@ static void call_forward_inherit(struct ast_channel *new_chan, struct ast_channe struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_channel *orig, int *timeout, struct ast_format_cap *cap, struct outgoing_helper *oh, int *outstate) { char tmpchan[256]; + char forwarder[AST_CHANNEL_NAME]; struct ast_channel *new_chan = NULL; char *data, *type; int cause = 0; @@ -5670,6 +5671,7 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan /* gather data and request the new forward channel */ ast_copy_string(tmpchan, ast_channel_call_forward(orig), sizeof(tmpchan)); + ast_copy_string(forwarder, ast_channel_name(orig), sizeof(forwarder)); if ((data = strchr(tmpchan, '/'))) { *data++ = '\0'; type = tmpchan; @@ -5713,6 +5715,7 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan ast_set_flag(ast_channel_flags(new_chan), AST_FLAG_ORIGINATED); ast_channel_lock_both(orig, new_chan); + pbx_builtin_setvar_helper(new_chan, "FORWARDERNAME", forwarder); ast_party_connected_line_copy(ast_channel_connected(new_chan), ast_channel_connected(orig)); ast_party_redirecting_copy(ast_channel_redirecting(new_chan), ast_channel_redirecting(orig)); ast_channel_req_accountcodes(new_chan, orig, AST_CHANNEL_REQUESTOR_REPLACEMENT); diff --git a/main/codec.c b/main/codec.c index 543d4d0bd..c253233bb 100644 --- a/main/codec.c +++ b/main/codec.c @@ -49,6 +49,32 @@ static int codec_id = 1; /*! \brief Registered codecs */ static struct ao2_container *codecs; +/*! + * \internal + * \brief Internal codec structure + * + * External codecs won't know about the format_name field so the public + * ast_codec structure has to leave it out. This structure will be used + * for the internal codecs. + * + */ +struct internal_ast_codec { + /*! \brief Public codec structure. Must remain first. */ + struct ast_codec external; + /*! \brief A format name for a default sane format using this codec */ + const char *format_name; +}; + +/*! + * \internal + * \brief Internal function for registration with format name + * + * This function is only used by codec.c and codec_builtin.c and + * will be removed in Asterisk 14 + */ +int __ast_codec_register_with_format(struct ast_codec *codec, const char *format_name, + struct ast_module *mod); + static int codec_hash(const void *obj, int flags) { const struct ast_codec *codec; @@ -113,7 +139,7 @@ static int codec_cmp(void *obj, void *arg, int flags) static char *show_codecs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator i; - struct ast_codec *codec; + struct internal_ast_codec *codec; switch (cmd) { case CLI_INIT: @@ -135,8 +161,8 @@ static char *show_codecs(struct ast_cli_entry *e, int cmd, struct ast_cli_args * "\tIt does not indicate anything about your configuration.\n"); } - ast_cli(a->fd, "%8s %5s %8s %s\n","ID","TYPE","NAME","DESCRIPTION"); - ast_cli(a->fd, "-----------------------------------------------------------------------------------\n"); + ast_cli(a->fd, "%8s %-5s %-12s %-16s %s\n","ID","TYPE","NAME","FORMAT","DESCRIPTION"); + ast_cli(a->fd, "------------------------------------------------------------------------------------------------\n"); ao2_rdlock(codecs); i = ao2_iterator_init(codecs, AO2_ITERATOR_DONTLOCK); @@ -144,19 +170,19 @@ static char *show_codecs(struct ast_cli_entry *e, int cmd, struct ast_cli_args * for (; (codec = ao2_iterator_next(&i)); ao2_ref(codec, -1)) { if (a->argc == 4) { if (!strcasecmp(a->argv[3], "audio")) { - if (codec->type != AST_MEDIA_TYPE_AUDIO) { + if (codec->external.type != AST_MEDIA_TYPE_AUDIO) { continue; } } else if (!strcasecmp(a->argv[3], "video")) { - if (codec->type != AST_MEDIA_TYPE_VIDEO) { + if (codec->external.type != AST_MEDIA_TYPE_VIDEO) { continue; } } else if (!strcasecmp(a->argv[3], "image")) { - if (codec->type != AST_MEDIA_TYPE_IMAGE) { + if (codec->external.type != AST_MEDIA_TYPE_IMAGE) { continue; } } else if (!strcasecmp(a->argv[3], "text")) { - if (codec->type != AST_MEDIA_TYPE_TEXT) { + if (codec->external.type != AST_MEDIA_TYPE_TEXT) { continue; } } else { @@ -164,11 +190,12 @@ static char *show_codecs(struct ast_cli_entry *e, int cmd, struct ast_cli_args * } } - ast_cli(a->fd, "%8u %5s %8s (%s)\n", - codec->id, - ast_codec_media_type2str(codec->type), - codec->name, - codec->description); + ast_cli(a->fd, "%8u %-5s %-12s %-16s (%s)\n", + codec->external.id, + ast_codec_media_type2str(codec->external.type), + codec->external.name, + S_OR(codec->format_name, "no cached format"), + codec->external.description); } ao2_iterator_destroy(&i); @@ -189,7 +216,7 @@ static int codec_id_cmp(void *obj, void *arg, int flags) static char *show_codec(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int type_punned_codec; - struct ast_codec *codec; + struct internal_ast_codec *codec; switch (cmd) { case CLI_INIT: @@ -216,7 +243,8 @@ static char *show_codec(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a return CLI_SUCCESS; } - ast_cli(a->fd, "%11u %s\n", (unsigned int) codec->id, codec->description); + ast_cli(a->fd, "%11u %s (%s)\n", (unsigned int) codec->external.id, codec->external.description, + S_OR(codec->format_name, "no format")); ao2_ref(codec, -1); @@ -261,8 +289,13 @@ static void codec_dtor(void *obj) int __ast_codec_register(struct ast_codec *codec, struct ast_module *mod) { + return __ast_codec_register_with_format(codec, NULL, mod); +} + +int __ast_codec_register_with_format(struct ast_codec *codec, const char *format_name, struct ast_module *mod) +{ SCOPED_AO2WRLOCK(lock, codecs); - struct ast_codec *codec_new; + struct internal_ast_codec *codec_new; /* Some types have specific requirements */ if (codec->type == AST_MEDIA_TYPE_UNKNOWN) { @@ -291,8 +324,9 @@ int __ast_codec_register(struct ast_codec *codec, struct ast_module *mod) codec->name, ast_codec_media_type2str(codec->type), codec->sample_rate); return -1; } - *codec_new = *codec; - codec_new->id = codec_id++; + codec_new->external = *codec; + codec_new->format_name = format_name; + codec_new->external.id = codec_id++; ao2_link_flags(codecs, codec_new, OBJ_NOLOCK); @@ -300,7 +334,7 @@ int __ast_codec_register(struct ast_codec *codec, struct ast_module *mod) ast_module_shutdown_ref(mod); ast_verb(2, "Registered '%s' codec '%s' at sample rate '%u' with id '%u'\n", - ast_codec_media_type2str(codec->type), codec->name, codec->sample_rate, codec_new->id); + ast_codec_media_type2str(codec->type), codec->name, codec->sample_rate, codec_new->external.id); ao2_ref(codec_new, -1); diff --git a/main/codec_builtin.c b/main/codec_builtin.c index 346b47b87..d3f65174c 100644 --- a/main/codec_builtin.c +++ b/main/codec_builtin.c @@ -38,6 +38,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/format_cache.h" #include "asterisk/frame.h" +int __ast_codec_register_with_format(struct ast_codec *codec, const char *format_name, + struct ast_module *mod); + enum frame_type { TYPE_HIGH, /* 0x0 */ TYPE_LOW, /* 0x1 */ @@ -774,7 +777,7 @@ static struct ast_codec t140 = { int __res_ ## __LINE__ = 0; \ struct ast_format *__fmt_ ## __LINE__; \ struct ast_codec *__codec_ ## __LINE__; \ - res |= __ast_codec_register(&(codec), NULL); \ + res |= __ast_codec_register_with_format(&(codec), (codec).name, NULL); \ __codec_ ## __LINE__ = ast_codec_get((codec).name, (codec).type, (codec).sample_rate); \ __fmt_ ## __LINE__ = __codec_ ## __LINE__ ? ast_format_create(__codec_ ## __LINE__) : NULL; \ res |= ast_format_cache_set(__fmt_ ## __LINE__); \ @@ -783,14 +786,14 @@ static struct ast_codec t140 = { __res_ ## __LINE__; \ }) -#define CODEC_REGISTER_AND_CACHE_NAMED(format_name, codec) \ +#define CODEC_REGISTER_AND_CACHE_NAMED(fmt_name, codec) \ ({ \ int __res_ ## __LINE__ = 0; \ struct ast_format *__fmt_ ## __LINE__; \ struct ast_codec *__codec_ ## __LINE__; \ - res |= __ast_codec_register(&(codec), NULL); \ + res |= __ast_codec_register_with_format(&(codec), fmt_name, NULL); \ __codec_ ## __LINE__ = ast_codec_get((codec).name, (codec).type, (codec).sample_rate); \ - __fmt_ ## __LINE__ = ast_format_create_named((format_name), __codec_ ## __LINE__); \ + __fmt_ ## __LINE__ = ast_format_create_named((fmt_name), __codec_ ## __LINE__); \ res |= ast_format_cache_set(__fmt_ ## __LINE__); \ ao2_ref(__fmt_ ## __LINE__, -1); \ ao2_ref(__codec_ ## __LINE__, -1); \ diff --git a/main/config_options.c b/main/config_options.c index c8988c984..cc8e218f8 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -97,6 +97,7 @@ static char *aco_option_type_string[] = { "IP Address", /* OPT_SOCKADDR_T, */ "String", /* OPT_STRINGFIELD_T, */ "Unsigned Integer", /* OPT_UINT_T, */ + "Boolean", /* OPT_YESNO_T, */ }; void *aco_pending_config(struct aco_info *info) @@ -139,6 +140,10 @@ static aco_option_handler ast_config_option_default_handler(enum aco_option_type switch(type) { case OPT_ACL_T: return acl_handler_fn; case OPT_BOOL_T: return bool_handler_fn; + /* Reading from config files, BOOL and YESNO are handled exactly the + * same. Their difference is in how they are rendered to users + */ + case OPT_YESNO_T: return bool_handler_fn; case OPT_BOOLFLAG_T: return boolflag_handler_fn; case OPT_CHAR_ARRAY_T: return chararray_handler_fn; case OPT_CODEC_T: return codec_handler_fn; diff --git a/main/dial.c b/main/dial.c index fe592033e..ffa440546 100644 --- a/main/dial.c +++ b/main/dial.c @@ -411,16 +411,24 @@ int ast_dial_prerun(struct ast_dial *dial, struct ast_channel *chan, struct ast_ } /*! \brief Helper function that does the beginning dialing per-appended channel */ -static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan, int async, const char *predial_string) +static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan, int async, const char *predial_string, struct ast_channel *forwarder_chan) { char numsubst[AST_MAX_EXTENSION]; int res = 1; + char forwarder[AST_CHANNEL_NAME]; /* If no owner channel exists yet execute pre-run */ if (!channel->owner && begin_dial_prerun(channel, chan, NULL, predial_string)) { return 0; } + if (forwarder_chan) { + ast_copy_string(forwarder, ast_channel_name(forwarder_chan), sizeof(forwarder)); + ast_channel_lock(channel->owner); + pbx_builtin_setvar_helper(channel->owner, "FORWARDERNAME", forwarder); + ast_channel_unlock(channel->owner); + } + /* Copy device string over */ ast_copy_string(numsubst, channel->device, sizeof(numsubst)); @@ -451,7 +459,7 @@ static int begin_dial(struct ast_dial *dial, struct ast_channel *chan, int async /* Iterate through channel list, requesting and calling each one */ AST_LIST_LOCK(&dial->channels); AST_LIST_TRAVERSE(&dial->channels, channel, list) { - success += begin_dial_channel(channel, chan, async, predial_string); + success += begin_dial_channel(channel, chan, async, predial_string, NULL); } AST_LIST_UNLOCK(&dial->channels); @@ -507,7 +515,7 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c channel->owner = NULL; /* Finally give it a go... send it out into the world */ - begin_dial_channel(channel, chan, chan ? 0 : 1, predial_string); + begin_dial_channel(channel, chan, chan ? 0 : 1, predial_string, original); ast_channel_publish_dial_forward(chan, original, channel->owner, NULL, "CANCEL", ast_channel_call_forward(original)); diff --git a/main/editline/Makefile.in b/main/editline/Makefile.in index 112b68b64..2be4333d5 100644 --- a/main/editline/Makefile.in +++ b/main/editline/Makefile.in @@ -187,7 +187,7 @@ distclean : clean # $(LIB_A) : $(BGCSRCS:.c=.o_a) $(CCSRCS:.c=.o_a) - $(AR) cru $@ $? + $(AR) cr $@ $? $(RANLIB) $@ $(LIB_S) : $(BGCSRCS:.c=.o_s) $(CCSRCS:.c=.o_s) diff --git a/main/http.c b/main/http.c index c343cb236..b2b35ff59 100644 --- a/main/http.c +++ b/main/http.c @@ -2220,7 +2220,7 @@ static int __ast_http_load(int reload) * the non-TLS bindaddress here. */ if (ast_sockaddr_isnull(&https_desc.local_address) && http_desc.accept_fd != -1) { - ast_sockaddr_copy(&https_desc.local_address, &https_desc.local_address); + ast_sockaddr_copy(&https_desc.local_address, &http_desc.local_address); /* Of course, we can't use the same port though. * Since no bind address was specified, we just use the * default TLS port diff --git a/main/manager.c b/main/manager.c index ba261e8e9..bc4804d89 100644 --- a/main/manager.c +++ b/main/manager.c @@ -100,6 +100,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/rtp_engine.h" #include "asterisk/format_cache.h" #include "asterisk/translate.h" +#include "asterisk/taskprocessor.h" /*** DOCUMENTATION <manager name="Ping" language="en_US"> @@ -8650,6 +8651,8 @@ static int manager_subscriptions_init(void) if (!stasis_router) { return -1; } + stasis_message_router_set_congestion_limits(stasis_router, -1, + 6 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL); res |= stasis_message_router_set_default(stasis_router, manager_default_msg_cb, NULL); diff --git a/main/say.c b/main/say.c index ef80dfa7d..f19ed71a1 100644 --- a/main/say.c +++ b/main/say.c @@ -5201,13 +5201,14 @@ int ast_say_date_with_format_it(struct ast_channel *chan, time_t t, const char * case 'I': case 'l': /* 12-Hour */ - if (tm.tm_hour == 0) + if (tm.tm_hour == 0) { ast_copy_string(nextmsg, "digits/12", sizeof(nextmsg)); - else if (tm.tm_hour > 12) + } else if (tm.tm_hour > 12) { snprintf(nextmsg, sizeof(nextmsg), "digits/%d", tm.tm_hour - 12); - else + } else { snprintf(nextmsg, sizeof(nextmsg), "digits/%d", tm.tm_hour); - res = wait_file(chan, ints, nextmsg, lang); + } + res = wait_file(chan, ints, nextmsg, lang); break; case 'H': case 'k': @@ -5227,11 +5228,12 @@ int ast_say_date_with_format_it(struct ast_channel *chan, time_t t, const char * case 'P': case 'p': /* AM/PM */ - if (tm.tm_hour > 11) + if (tm.tm_hour > 11) { ast_copy_string(nextmsg, "digits/p-m", sizeof(nextmsg)); - else + } else { ast_copy_string(nextmsg, "digits/a-m", sizeof(nextmsg)); - res = wait_file(chan, ints, nextmsg, lang); + } + res = wait_file(chan, ints, nextmsg, lang); break; case 'Q': /* Shorthand for "Today", "Yesterday", or ABdY */ @@ -7948,9 +7950,9 @@ int ast_say_date_with_format_ja(struct ast_channel *chan, time_t time, const cha /* NOTE: if you add more options here, please try to be consistent with strftime(3) */ case '\'': /* Literal name of a sound file */ - sndoffset=0; - for (sndoffset=0 ; (format[++offset] != '\'') && (sndoffset < 256) ; sndoffset++) + for (sndoffset = 0 ; (format[++offset] != '\'') && (sndoffset < sizeof(sndfile) - 1) ; sndoffset++) { sndfile[sndoffset] = format[offset]; + } sndfile[sndoffset] = '\0'; res = wait_file(chan,ints,sndfile,lang); break; diff --git a/main/sorcery.c b/main/sorcery.c index 3a29cfa58..bfc768879 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -290,6 +290,12 @@ static int bool_handler_fn(const void *obj, const intptr_t *args, char **buf) return !(*buf = ast_strdup(*field ? "true" : "false")) ? -1 : 0; } +static int yesno_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + unsigned int *field = (unsigned int *)(obj + args[0]); + return !(*buf = ast_strdup(*field ? "yes" : "no")) ? -1 : 0; +} + static int sockaddr_handler_fn(const void *obj, const intptr_t *args, char **buf) { struct ast_sockaddr *field = (struct ast_sockaddr *)(obj + args[0]); @@ -313,6 +319,7 @@ static sorcery_field_handler sorcery_field_default_handler(enum aco_option_type { switch(type) { case OPT_BOOL_T: return bool_handler_fn; + case OPT_YESNO_T: return yesno_handler_fn; case OPT_CHAR_ARRAY_T: return chararray_handler_fn; case OPT_CODEC_T: return codec_handler_fn; case OPT_DOUBLE_T: return double_handler_fn; @@ -1161,6 +1168,20 @@ int __ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, return 0; } +int ast_sorcery_object_set_congestion_levels(struct ast_sorcery *sorcery, const char *type, long low_water, long high_water) +{ + struct ast_sorcery_object_type *object_type; + int res = -1; + + object_type = ao2_find(sorcery->types, type, OBJ_SEARCH_KEY); + if (object_type) { + res = ast_taskprocessor_alert_set_levels(object_type->serializer, + low_water, high_water); + ao2_ref(object_type, -1); + } + return res; +} + void ast_sorcery_object_set_copy_handler(struct ast_sorcery *sorcery, const char *type, sorcery_copy_handler copy) { RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); diff --git a/main/stasis.c b/main/stasis.c index 4fb69033f..bbafb69e1 100644 --- a/main/stasis.c +++ b/main/stasis.c @@ -564,6 +564,18 @@ struct stasis_subscription *stasis_unsubscribe(struct stasis_subscription *sub) return NULL; } +int stasis_subscription_set_congestion_limits(struct stasis_subscription *subscription, + long low_water, long high_water) +{ + int res = -1; + + if (subscription) { + res = ast_taskprocessor_alert_set_levels(subscription->mailbox, + low_water, high_water); + } + return res; +} + void stasis_subscription_join(struct stasis_subscription *subscription) { if (subscription) { diff --git a/main/stasis_message_router.c b/main/stasis_message_router.c index 26df76c53..cf0ac787e 100644 --- a/main/stasis_message_router.c +++ b/main/stasis_message_router.c @@ -289,6 +289,18 @@ void stasis_message_router_publish_sync(struct stasis_message_router *router, ao2_cleanup(router); } +int stasis_message_router_set_congestion_limits(struct stasis_message_router *router, + long low_water, long high_water) +{ + int res = -1; + + if (router) { + res = stasis_subscription_set_congestion_limits(router->subscription, + low_water, high_water); + } + return res; +} + int stasis_message_router_add(struct stasis_message_router *router, struct stasis_message_type *message_type, stasis_subscription_cb callback, void *data) diff --git a/main/stdtime/localtime.c b/main/stdtime/localtime.c index 702edbe85..9cdf614d5 100644 --- a/main/stdtime/localtime.c +++ b/main/stdtime/localtime.c @@ -1849,13 +1849,14 @@ void ast_get_dst_info(const time_t * const timep, int *dst_enabled, time_t *dst_ *dst_enabled = 0; /* Find where I can get gmtoff */ i = 0; - while (sp->ttis[i].tt_isdst) + while (sp->ttis[i].tt_isdst) { if (++i >= sp->typecnt) { - i = 0; - break; + i = 0; + break; } - *gmt_off = sp->ttis[i].tt_gmtoff; - return; + } + *gmt_off = sp->ttis[i].tt_gmtoff; + return; } for (i = 1; i < sp->timecnt; ++i) { diff --git a/main/taskprocessor.c b/main/taskprocessor.c index 1ba0c8a2f..7ce3e4f16 100644 --- a/main/taskprocessor.c +++ b/main/taskprocessor.c @@ -76,6 +76,10 @@ struct ast_taskprocessor { void *local_data; /*! \brief Taskprocessor current queue size */ long tps_queue_size; + /*! \brief Taskprocessor low water clear alert level */ + long tps_queue_low; + /*! \brief Taskprocessor high water alert trigger level */ + long tps_queue_high; /*! \brief Taskprocessor queue */ AST_LIST_HEAD_NOLOCK(tps_queue, tps_task) tps_queue; struct ast_taskprocessor_listener *listener; @@ -85,6 +89,8 @@ struct ast_taskprocessor { unsigned int executing:1; /*! Indicates that a high water warning has been issued on this task processor */ unsigned int high_water_warned:1; + /*! Indicates that a high water alert is active on this taskprocessor */ + unsigned int high_water_alert:1; }; /*! @@ -121,15 +127,9 @@ static int tps_hash_cb(const void *obj, const int flags); /*! \brief The astobj2 compare callback for taskprocessors */ static int tps_cmp_cb(void *obj, void *arg, int flags); -/*! \brief Destroy the taskprocessor when its refcount reaches zero */ -static void tps_taskprocessor_destroy(void *tps); - /*! \brief CLI <example>taskprocessor ping <blah></example> handler function */ static int tps_ping_handler(void *datap); -/*! \brief Remove the front task off the taskprocessor queue */ -static struct tps_task *tps_taskprocessor_pop(struct ast_taskprocessor *tps); - static char *cli_tps_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); @@ -472,8 +472,8 @@ static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_arg struct ao2_container *sorted_tps; struct ast_taskprocessor *tps; struct ao2_iterator iter; -#define FMT_HEADERS "%-45s %10s %10s %10s\n" -#define FMT_FIELDS "%-45s %10lu %10lu %10lu\n" +#define FMT_HEADERS "%-45s %10s %10s %10s %10s %10s\n" +#define FMT_FIELDS "%-45s %10lu %10lu %10lu %10lu %10lu\n" switch (cmd) { case CLI_INIT: @@ -498,7 +498,7 @@ static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_arg return CLI_FAILURE; } - ast_cli(a->fd, "\n" FMT_HEADERS, "Processor", "Processed", "In Queue", "Max Depth"); + ast_cli(a->fd, "\n" FMT_HEADERS, "Processor", "Processed", "In Queue", "Max Depth", "Low water", "High water"); tcount = 0; iter = ao2_iterator_init(sorted_tps, AO2_ITERATOR_UNLINK); while ((tps = ao2_iterator_next(&iter))) { @@ -511,7 +511,8 @@ static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_arg maxqsize = 0; processed = 0; } - ast_cli(a->fd, FMT_FIELDS, name, processed, qsize, maxqsize); + ast_cli(a->fd, FMT_FIELDS, name, processed, qsize, maxqsize, + tps->tps_queue_low, tps->tps_queue_high); ast_taskprocessor_unreference(tps); ++tcount; } @@ -539,28 +540,106 @@ static int tps_cmp_cb(void *obj, void *arg, int flags) return !strcasecmp(lhs->name, rhsname) ? CMP_MATCH | CMP_STOP : 0; } +/*! Count of the number of taskprocessors in high water alert. */ +static unsigned int tps_alert_count; + +/*! Access protection for tps_alert_count */ +AST_RWLOCK_DEFINE_STATIC(tps_alert_lock); + +/*! + * \internal + * \brief Add a delta to tps_alert_count with protection. + * \since 13.10.0 + * + * \param tps Taskprocessor updating queue water mark alert trigger. + * \param delta The amount to add to tps_alert_count. + * + * \return Nothing + */ +static void tps_alert_add(struct ast_taskprocessor *tps, int delta) +{ + unsigned int old; + + ast_rwlock_wrlock(&tps_alert_lock); + old = tps_alert_count; + tps_alert_count += delta; + if (DEBUG_ATLEAST(3) + /* and tps_alert_count becomes zero or non-zero */ + && !old != !tps_alert_count) { + ast_log(LOG_DEBUG, "Taskprocessor '%s' %s the high water alert.\n", + tps->name, tps_alert_count ? "triggered" : "cleared"); + } + ast_rwlock_unlock(&tps_alert_lock); +} + +unsigned int ast_taskprocessor_alert_get(void) +{ + unsigned int count; + + ast_rwlock_rdlock(&tps_alert_lock); + count = tps_alert_count; + ast_rwlock_unlock(&tps_alert_lock); + + return count; +} + +int ast_taskprocessor_alert_set_levels(struct ast_taskprocessor *tps, long low_water, long high_water) +{ + if (!tps || high_water < 0 || high_water < low_water) { + return -1; + } + + if (low_water < 0) { + /* Set low water level to 90% of high water level */ + low_water = (high_water * 9) / 10; + } + + ao2_lock(tps); + + tps->tps_queue_low = low_water; + tps->tps_queue_high = high_water; + + if (tps->high_water_alert) { + if (!tps->tps_queue_size || tps->tps_queue_size < low_water) { + /* Update water mark alert immediately */ + tps->high_water_alert = 0; + tps_alert_add(tps, -1); + } + } else { + if (high_water <= tps->tps_queue_size) { + /* Update water mark alert immediately */ + tps->high_water_alert = 1; + tps_alert_add(tps, +1); + } + } + + ao2_unlock(tps); + + return 0; +} + /* destroy the taskprocessor */ -static void tps_taskprocessor_destroy(void *tps) +static void tps_taskprocessor_dtor(void *tps) { struct ast_taskprocessor *t = tps; struct tps_task *task; - if (!tps) { - ast_log(LOG_ERROR, "missing taskprocessor\n"); - return; + while ((task = AST_LIST_REMOVE_HEAD(&t->tps_queue, list))) { + tps_task_free(task); } - ast_debug(1, "destroying taskprocessor '%s'\n", t->name); - /* free it */ + t->tps_queue_size = 0; + + if (t->high_water_alert) { + t->high_water_alert = 0; + tps_alert_add(t, -1); + } + ast_free(t->stats); t->stats = NULL; ast_free((char *) t->name); - if (t->listener) { - ao2_ref(t->listener, -1); - t->listener = NULL; - } - while ((task = AST_LIST_REMOVE_HEAD(&t->tps_queue, list))) { - tps_task_free(task); - } + t->name = NULL; + ao2_cleanup(t->listener); + t->listener = NULL; } /* pop the front task and return it */ @@ -569,7 +648,11 @@ static struct tps_task *tps_taskprocessor_pop(struct ast_taskprocessor *tps) struct tps_task *task; if ((task = AST_LIST_REMOVE_HEAD(&tps->tps_queue, list))) { - tps->tps_queue_size--; + --tps->tps_queue_size; + if (tps->high_water_alert && tps->tps_queue_size <= tps->tps_queue_low) { + tps->high_water_alert = 0; + tps_alert_add(tps, -1); + } } return task; } @@ -648,19 +731,22 @@ static void *default_listener_pvt_alloc(void) static struct ast_taskprocessor *__allocate_taskprocessor(const char *name, struct ast_taskprocessor_listener *listener) { - RAII_VAR(struct ast_taskprocessor *, p, - ao2_alloc(sizeof(*p), tps_taskprocessor_destroy), ao2_cleanup); + struct ast_taskprocessor *p; + p = ao2_alloc(sizeof(*p), tps_taskprocessor_dtor); if (!p) { ast_log(LOG_WARNING, "failed to create taskprocessor '%s'\n", name); return NULL; } - if (!(p->stats = ast_calloc(1, sizeof(*p->stats)))) { - ast_log(LOG_WARNING, "failed to create taskprocessor stats for '%s'\n", name); - return NULL; - } - if (!(p->name = ast_strdup(name))) { + /* Set default congestion water level alert triggers. */ + p->tps_queue_low = (AST_TASKPROCESSOR_HIGH_WATER_LEVEL * 9) / 10; + p->tps_queue_high = AST_TASKPROCESSOR_HIGH_WATER_LEVEL; + + p->stats = ast_calloc(1, sizeof(*p->stats)); + p->name = ast_strdup(name); + if (!p->stats || !p->name) { + ao2_ref(p, -1); return NULL; } @@ -675,22 +761,18 @@ static struct ast_taskprocessor *__allocate_taskprocessor(const char *name, stru if (!(ao2_link(tps_singletons, p))) { ast_log(LOG_ERROR, "Failed to add taskprocessor '%s' to container\n", p->name); listener->tps = NULL; - ao2_ref(p, -1); + ao2_ref(p, -2); return NULL; } if (p->listener->callbacks->start(p->listener)) { - ast_log(LOG_ERROR, "Unable to start taskprocessor listener for taskprocessor %s\n", p->name); + ast_log(LOG_ERROR, "Unable to start taskprocessor listener for taskprocessor %s\n", + p->name); ast_taskprocessor_unreference(p); return NULL; } - /* RAII_VAR will decrement the refcount at the end of the function. - * Since we want to pass back a reference to p, we bump the refcount - */ - ao2_ref(p, +1); return p; - } /* Provide a reference to a taskprocessor. Create the taskprocessor if necessary, but don't @@ -799,10 +881,16 @@ static int taskprocessor_push(struct ast_taskprocessor *tps, struct tps_task *t) AST_LIST_INSERT_TAIL(&tps->tps_queue, t, list); previous_size = tps->tps_queue_size++; - if (previous_size >= AST_TASKPROCESSOR_HIGH_WATER_LEVEL && !tps->high_water_warned) { - ast_log(LOG_WARNING, "The '%s' task processor queue reached %d scheduled tasks.\n", - tps->name, previous_size); - tps->high_water_warned = 1; + if (previous_size >= tps->tps_queue_high) { + if (!tps->high_water_warned) { + tps->high_water_warned = 1; + ast_log(LOG_WARNING, "The '%s' task processor queue reached %d scheduled tasks.\n", + tps->name, previous_size); + } + if (!tps->high_water_alert) { + tps->high_water_alert = 1; + tps_alert_add(tps, +1); + } } /* The currently executing task counts as still in queue */ diff --git a/res/ael/pval.c b/res/ael/pval.c index d72ef0d59..07545f659 100644 --- a/res/ael/pval.c +++ b/res/ael/pval.c @@ -3355,9 +3355,9 @@ static int gen_prios(struct ael_extension *exten, char *label, pval *statement, #ifdef OLD_RAND_ACTION struct ael_priority *rand_test, *rand_end, *rand_skip; #endif - char *buf1; - char *buf2; - char *new_label; + RAII_VAR(char *, buf1, NULL, free); + RAII_VAR(char *, buf2, NULL, free); + RAII_VAR(char *, new_label, NULL, free); char *strp, *strp2; int default_exists; int local_control_statement_count; @@ -4191,9 +4191,6 @@ static int gen_prios(struct ael_extension *exten, char *label, pval *statement, break; } } - free(buf1); - free(buf2); - free(new_label); return 0; } @@ -5052,7 +5049,10 @@ int pvalCheckType( pval *p, char *funcname, pvaltype type ) pval *pvalCreateNode( pvaltype type ) { pval *p = calloc(1,sizeof(pval)); /* why, oh why, don't I use ast_calloc? Way, way, way too messy if I do! */ - p->type = type; /* remember, this can be used externally or internally to asterisk */ + /* remember, this can be used externally or internally to asterisk */ + if (p) { + p->type = type; + } return p; } @@ -5413,14 +5413,30 @@ void pvalIncludesAddInclude( pval *p, const char *include ) void pvalIncludesAddIncludeWithTimeConstraints( pval *p, const char *include, char *hour_range, char *dom_range, char *dow_range, char *month_range ) { - pval *hr = pvalCreateNode(PV_WORD); - pval *dom = pvalCreateNode(PV_WORD); - pval *dow = pvalCreateNode(PV_WORD); - pval *mon = pvalCreateNode(PV_WORD); - pval *s = pvalCreateNode(PV_WORD); - - if (!pvalCheckType(p, "pvalIncludeAddIncludeWithTimeConstraints", PV_INCLUDES)) + pval *hr; + pval *dom; + pval *dow; + pval *mon; + pval *s; + + if (!pvalCheckType(p, "pvalIncludeAddIncludeWithTimeConstraints", PV_INCLUDES)) { + return; + } + + hr = pvalCreateNode(PV_WORD); + dom = pvalCreateNode(PV_WORD); + dow = pvalCreateNode(PV_WORD); + mon = pvalCreateNode(PV_WORD); + s = pvalCreateNode(PV_WORD); + + if (!hr || !dom || !dow || !mon || !s) { + destroy_pval(hr); + destroy_pval(dom); + destroy_pval(dow); + destroy_pval(mon); + destroy_pval(s); return; + } s->u1.str = (char *)include; p->u1.list = linku1(p->u1.list, s); @@ -5667,12 +5683,28 @@ char* pvalIfGetCondition( pval *p ) void pvalIfTimeSetCondition( pval *p, char *hour_range, char *dow_range, char *dom_range, char *mon_range ) /* time range format: 24-hour format begin-end|dow range|dom range|month range */ { - pval *hr = pvalCreateNode(PV_WORD); - pval *dow = pvalCreateNode(PV_WORD); - pval *dom = pvalCreateNode(PV_WORD); - pval *mon = pvalCreateNode(PV_WORD); - if (!pvalCheckType(p, "pvalIfTimeSetCondition", PV_IFTIME)) + pval *hr; + pval *dow; + pval *dom; + pval *mon; + + if (!pvalCheckType(p, "pvalIfTimeSetCondition", PV_IFTIME)) { return; + } + + hr = pvalCreateNode(PV_WORD); + dow = pvalCreateNode(PV_WORD); + dom = pvalCreateNode(PV_WORD); + mon = pvalCreateNode(PV_WORD); + + if (!hr || !dom || !dow || !mon) { + destroy_pval(hr); + destroy_pval(dom); + destroy_pval(dow); + destroy_pval(mon); + return; + } + pvalWordSetString(hr, hour_range); pvalWordSetString(dow, dow_range); pvalWordSetString(dom, dom_range); diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c index a86f3129c..39709d022 100644 --- a/res/ari/resource_bridges.c +++ b/res/ari/resource_bridges.c @@ -278,6 +278,7 @@ struct bridge_channel_control_thread_data { struct ast_channel *bridge_channel; struct stasis_app_control *control; struct stasis_forward *forward; + char bridge_id[0]; }; static void *bridge_channel_control_thread(void *data) @@ -286,6 +287,7 @@ static void *bridge_channel_control_thread(void *data) struct ast_channel *bridge_channel = thread_data->bridge_channel; struct stasis_app_control *control = thread_data->control; struct stasis_forward *forward = thread_data->forward; + char *bridge_id = ast_strdupa(thread_data->bridge_id); RAII_VAR(struct ast_callid *, callid, ast_channel_callid(bridge_channel), ast_callid_cleanup); @@ -299,6 +301,7 @@ static void *bridge_channel_control_thread(void *data) stasis_app_control_execute_until_exhausted(bridge_channel, control); stasis_app_control_flush_queue(control); + stasis_app_bridge_playback_channel_remove(bridge_id, control); stasis_forward_cancel(forward); ao2_cleanup(control); ast_hangup(bridge_channel); @@ -464,8 +467,9 @@ static void ari_bridges_play_new(const char *args_media, } /* Give play_channel and control reference to the thread data */ - thread_data = ast_calloc(1, sizeof(*thread_data)); + thread_data = ast_malloc(sizeof(*thread_data) + strlen(bridge->uniqueid) + 1); if (!thread_data) { + stasis_app_bridge_playback_channel_remove((char *)bridge->uniqueid, control); ast_ari_response_alloc_failed(response); return; } @@ -473,8 +477,11 @@ static void ari_bridges_play_new(const char *args_media, thread_data->bridge_channel = play_channel; thread_data->control = control; thread_data->forward = channel_forward; + /* Safe */ + strcpy(thread_data->bridge_id, bridge->uniqueid); if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) { + stasis_app_bridge_playback_channel_remove((char *)bridge->uniqueid, control); ast_ari_response_alloc_failed(response); ast_free(thread_data); return; diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index 9e2db9de6..6baac7a4e 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -912,6 +912,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, const char *args_channel_id, const char *args_other_channel_id, const char *args_originator, + const char *args_formats, struct ast_ari_response *response) { char *dialtech; @@ -930,6 +931,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, }; struct ari_origination *origination; pthread_t thread; + struct ast_format_cap *format_cap = NULL; if ((assignedids.uniqueid && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid)) || (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) { @@ -944,6 +946,12 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, return; } + if (!ast_strlen_zero(args_originator) && !ast_strlen_zero(args_formats)) { + ast_ari_response_error(response, 400, "Bad Request", + "Originator and formats can't both be specified"); + return; + } + dialtech = ast_strdupa(args_endpoint); if ((stuff = strchr(dialtech, '/'))) { *stuff++ = '\0'; @@ -1066,7 +1074,41 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, } } - if (ast_dial_prerun(dial, other, NULL)) { + if (!ast_strlen_zero(args_formats)) { + char *format_name; + char *formats_copy = ast_strdupa(args_formats); + + if (!(format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ast_ari_response_alloc_failed(response); + ast_dial_destroy(dial); + ast_free(origination); + ast_channel_cleanup(other); + return; + } + + while ((format_name = ast_strip(strsep(&formats_copy, ",")))) { + struct ast_format *fmt = ast_format_cache_get(format_name); + + if (!fmt || ast_format_cap_append(format_cap, fmt, 0)) { + if (!fmt) { + ast_ari_response_error( + response, 400, "Bad Request", + "Provided format (%s) was not found", format_name); + } else { + ast_ari_response_alloc_failed(response); + } + ast_dial_destroy(dial); + ast_free(origination); + ast_channel_cleanup(other); + ao2_ref(format_cap, -1); + ao2_cleanup(fmt); + return; + } + ao2_ref(fmt, -1); + } + } + + if (ast_dial_prerun(dial, other, format_cap)) { ast_ari_response_alloc_failed(response); ast_dial_destroy(dial); ast_free(origination); @@ -1075,6 +1117,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, } ast_channel_cleanup(other); + ao2_cleanup(format_cap); chan = ast_dial_get_channel(dial, 0); if (!chan) { @@ -1215,6 +1258,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers, args->channel_id, args->other_channel_id, args->originator, + args->formats, response); ast_variables_destroy(variables); } @@ -1251,6 +1295,7 @@ void ast_ari_channels_originate(struct ast_variable *headers, args->channel_id, args->other_channel_id, args->originator, + args->formats, response); ast_variables_destroy(variables); } diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h index 4d3ad5f8b..5bb6f7f1e 100644 --- a/res/ari/resource_channels.h +++ b/res/ari/resource_channels.h @@ -78,6 +78,8 @@ struct ast_ari_channels_originate_args { const char *other_channel_id; /*! The unique id of the channel which is originating this one. */ const char *originator; + /*! The format name capability list to use if originator is not specified. Ex. "ulaw,slin16". Format names an be found with "core show codecs". */ + const char *formats; }; /*! * \brief Body parsing function for /channels. @@ -141,6 +143,8 @@ struct ast_ari_channels_originate_with_id_args { const char *other_channel_id; /*! The unique id of the channel which is originating this one. */ const char *originator; + /*! The format name capability list to use if originator is not specified. Ex. "ulaw,slin16". Format names an be found with "core show codecs". */ + const char *formats; }; /*! * \brief Body parsing function for /channels/{channelId}. diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index d1ae80196..a14a9c8cb 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -157,6 +157,10 @@ int ast_ari_channels_originate_parse_body( if (field) { args->originator = ast_json_string_get(field); } + field = ast_json_object_get(body, "formats"); + if (field) { + args->formats = ast_json_string_get(field); + } return 0; } @@ -217,6 +221,9 @@ static void ast_ari_channels_originate_cb( if (strcmp(i->name, "originator") == 0) { args.originator = (i->value); } else + if (strcmp(i->name, "formats") == 0) { + args.formats = (i->value); + } else {} } /* Look for a JSON request entity */ @@ -377,6 +384,10 @@ int ast_ari_channels_originate_with_id_parse_body( if (field) { args->originator = ast_json_string_get(field); } + field = ast_json_object_get(body, "formats"); + if (field) { + args->formats = ast_json_string_get(field); + } return 0; } @@ -434,6 +445,9 @@ static void ast_ari_channels_originate_with_id_cb( if (strcmp(i->name, "originator") == 0) { args.originator = (i->value); } else + if (strcmp(i->name, "formats") == 0) { + args.formats = (i->value); + } else {} } for (i = path_vars; i; i = i->next) { diff --git a/res/res_fax.c b/res/res_fax.c index 6282b13d7..ad6e2386c 100644 --- a/res/res_fax.c +++ b/res/res_fax.c @@ -2846,11 +2846,8 @@ static struct ast_fax_session *fax_v21_session_new (struct ast_channel *chan) { } v21_details->caps = AST_FAX_TECH_V21_DETECT; - if (!(v21_session = fax_session_new(v21_details, chan, NULL, NULL))) { - ao2_ref(v21_details, -1); - return NULL; - } - + v21_session = fax_session_new(v21_details, chan, NULL, NULL); + ao2_ref(v21_details, -1); return v21_session; } diff --git a/res/res_format_attr_siren14.c b/res/res_format_attr_siren14.c new file mode 100644 index 000000000..dea13aec6 --- /dev/null +++ b/res/res_format_attr_siren14.c @@ -0,0 +1,94 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Digium, Inc. + * + * Joshua Colp <jcolp@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 Siren14 format attribute interface + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/format.h" + +/* Destroy is a required callback and must exist */ +static void siren14_destroy(struct ast_format *format) +{ +} + +/* Clone is a required callback and must exist */ +static int siren14_clone(const struct ast_format *src, struct ast_format *dst) +{ + return 0; +} + +static struct ast_format *siren14_parse_sdp_fmtp(const struct ast_format *format, const char *attributes) +{ + unsigned int val; + + if (sscanf(attributes, "bitrate=%30u", &val) == 1) { + if (val != 48000) { + ast_log(LOG_WARNING, "Got siren14 offer at %u bps, but only 48000 bps supported; ignoring.\n", val); + return NULL; + } + } + + /* We aren't modifying the format and once passed back it won't be touched, so use what we were given */ + return ao2_bump((struct ast_format *)format); +} + +static void siren14_generate_sdp_fmtp(const struct ast_format *format, unsigned int payload, struct ast_str **str) +{ + ast_str_append(str, 0, "a=fmtp:%u bitrate=48000\r\n", payload); +} + +static struct ast_format_interface siren14_interface = { + .format_destroy = siren14_destroy, + .format_clone = siren14_clone, + .format_parse_sdp_fmtp = siren14_parse_sdp_fmtp, + .format_generate_sdp_fmtp = siren14_generate_sdp_fmtp, +}; + +static int load_module(void) +{ + if (ast_format_interface_register("siren14", &siren14_interface)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Siren14 Format Attribute Module", + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_format_attr_siren7.c b/res/res_format_attr_siren7.c new file mode 100644 index 000000000..840de4886 --- /dev/null +++ b/res/res_format_attr_siren7.c @@ -0,0 +1,94 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Digium, Inc. + * + * Joshua Colp <jcolp@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 Siren7 format attribute interface + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/format.h" + +/* Destroy is a required callback and must exist */ +static void siren7_destroy(struct ast_format *format) +{ +} + +/* Clone is a required callback and must exist */ +static int siren7_clone(const struct ast_format *src, struct ast_format *dst) +{ + return 0; +} + +static struct ast_format *siren7_parse_sdp_fmtp(const struct ast_format *format, const char *attributes) +{ + unsigned int val; + + if (sscanf(attributes, "bitrate=%30u", &val) == 1) { + if (val != 32000) { + ast_log(LOG_WARNING, "Got Siren7 offer at %u bps, but only 32000 bps supported; ignoring.\n", val); + return NULL; + } + } + + /* We aren't modifying the format and once passed back it won't be touched, so use what we were given */ + return ao2_bump((struct ast_format *)format); +} + +static void siren7_generate_sdp_fmtp(const struct ast_format *format, unsigned int payload, struct ast_str **str) +{ + ast_str_append(str, 0, "a=fmtp:%u bitrate=32000\r\n", payload); +} + +static struct ast_format_interface siren7_interface = { + .format_destroy = siren7_destroy, + .format_clone = siren7_clone, + .format_parse_sdp_fmtp = siren7_parse_sdp_fmtp, + .format_generate_sdp_fmtp = siren7_generate_sdp_fmtp, +}; + +static int load_module(void) +{ + if (ast_format_interface_register("siren7", &siren7_interface)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Siren7 Format Attribute Module", + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_hep.c b/res/res_hep.c index 723b27df8..15e779012 100644 --- a/res/res_hep.c +++ b/res/res_hep.c @@ -409,9 +409,21 @@ enum hep_uuid_type hepv3_get_uuid_type(void) { RAII_VAR(struct module_config *, config, ao2_global_obj_ref(global_config), ao2_cleanup); + if (!config) { + /* Well, that's unfortunate. Return something. */ + return HEP_UUID_TYPE_CALL_ID; + } + return config->general->uuid_type; } +int hepv3_is_loaded(void) +{ + RAII_VAR(struct module_config *, config, ao2_global_obj_ref(global_config), ao2_cleanup); + + return (config != NULL) ? 1 : 0; +} + struct hepv3_capture_info *hepv3_create_capture_info(const void *payload, size_t len) { struct hepv3_capture_info *info; diff --git a/res/res_hep.exports.in b/res/res_hep.exports.in index df0f2b4f7..e318ac97f 100644 --- a/res/res_hep.exports.in +++ b/res/res_hep.exports.in @@ -3,6 +3,7 @@ LINKER_SYMBOL_PREFIX*hepv3_send_packet; LINKER_SYMBOL_PREFIX*hepv3_create_capture_info; LINKER_SYMBOL_PREFIX*hepv3_get_uuid_type; + LINKER_SYMBOL_PREFIX*hepv3_is_loaded; local: *; }; diff --git a/res/res_hep_pjsip.c b/res/res_hep_pjsip.c index 936db9300..8f5baa2cb 100644 --- a/res/res_hep_pjsip.c +++ b/res/res_hep_pjsip.c @@ -210,6 +210,11 @@ static int load_module(void) { CHECK_PJSIP_MODULE_LOADED(); + if (!ast_module_check("res_hep.so") || !hepv3_is_loaded()) { + ast_log(AST_LOG_WARNING, "res_hep is not loaded or running; declining module load\n"); + return AST_MODULE_LOAD_DECLINE; + } + ast_sip_register_service(&logging_module); return AST_MODULE_LOAD_SUCCESS; } diff --git a/res/res_hep_rtcp.c b/res/res_hep_rtcp.c index 49a92539f..d77b19c92 100644 --- a/res/res_hep_rtcp.c +++ b/res/res_hep_rtcp.c @@ -149,6 +149,10 @@ static void rtp_topic_handler(void *data, struct stasis_subscription *sub, struc static int load_module(void) { + if (!ast_module_check("res_hep.so") || !hepv3_is_loaded()) { + ast_log(AST_LOG_WARNING, "res_hep is not loaded or running; declining module load\n"); + return AST_MODULE_LOAD_DECLINE; + } stasis_rtp_subscription = stasis_subscribe(ast_rtp_topic(), rtp_topic_handler, NULL); diff --git a/res/res_odbc.c b/res/res_odbc.c index 17b7a76c8..b2204ff09 100644 --- a/res/res_odbc.c +++ b/res/res_odbc.c @@ -78,10 +78,19 @@ struct odbc_class unsigned int forcecommit:1; /*!< Should uncommitted transactions be auto-committed on handle release? */ unsigned int isolation; /*!< Flags for how the DB should deal with data in other, uncommitted transactions */ unsigned int conntimeout; /*!< Maximum time the connection process should take */ + unsigned int maxconnections; /*!< Maximum number of allowed connections */ /*! When a connection fails, cache that failure for how long? */ struct timeval negative_connection_cache; /*! When a connection fails, when did that last occur? */ struct timeval last_negative_connect; + /*! A pool of available connections */ + AST_LIST_HEAD_NOLOCK(, odbc_obj) connections; + /*! Lock to protect the connections */ + ast_mutex_t lock; + /*! Condition to notify any pending connection requesters */ + ast_cond_t cond; + /*! The total number of current connections */ + size_t connection_cnt; }; static struct ao2_container *class_container; @@ -90,7 +99,7 @@ static AST_RWLIST_HEAD_STATIC(odbc_tables, odbc_cache_tables); static odbc_status odbc_obj_connect(struct odbc_obj *obj); static odbc_status odbc_obj_disconnect(struct odbc_obj *obj); -static int odbc_register_class(struct odbc_class *class, int connect); +static void odbc_register_class(struct odbc_class *class, int connect); AST_THREADSTORAGE(errors_buf); @@ -157,6 +166,8 @@ int ast_odbc_text2isolation(const char *txt) static void odbc_class_destructor(void *data) { struct odbc_class *class = data; + struct odbc_obj *obj; + /* Due to refcounts, we can safely assume that any objects with a reference * to us will prevent our destruction, so we don't need to worry about them. */ @@ -169,7 +180,14 @@ static void odbc_class_destructor(void *data) if (class->sanitysql) { ast_free(class->sanitysql); } + + while ((obj = AST_LIST_REMOVE_HEAD(&class->connections, list))) { + ao2_ref(obj, -1); + } + SQLFreeHandle(SQL_HANDLE_ENV, class->env); + ast_mutex_destroy(&class->lock); + ast_cond_destroy(&class->cond); } static int null_hash_fn(const void *obj, const int flags) @@ -180,21 +198,23 @@ static int null_hash_fn(const void *obj, const int flags) static void odbc_obj_destructor(void *data) { struct odbc_obj *obj = data; - struct odbc_class *class = obj->parent; - obj->parent = NULL; + odbc_obj_disconnect(obj); - ao2_ref(class, -1); } -static void destroy_table_cache(struct odbc_cache_tables *table) { +static void destroy_table_cache(struct odbc_cache_tables *table) +{ struct odbc_cache_columns *col; + ast_debug(1, "Destroying table cache for %s\n", table->table); + AST_RWLIST_WRLOCK(&table->columns); while ((col = AST_RWLIST_REMOVE_HEAD(&table->columns, list))) { ast_free(col); } AST_RWLIST_UNLOCK(&table->columns); AST_RWLIST_HEAD_DESTROY(&table->columns); + ast_free(table); } @@ -370,18 +390,19 @@ SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_c * We must therefore redo everything when we establish a new * connection. */ stmt = prepare_cb(obj, data); + if (!stmt) { + return NULL; + } - if (stmt) { - res = SQLExecute(stmt); - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) { - if (res == SQL_ERROR) { - ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute"); - } - - ast_log(LOG_WARNING, "SQL Execute error %d!\n", res); - SQLFreeHandle(SQL_HANDLE_STMT, stmt); - stmt = NULL; + res = SQLExecute(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) { + if (res == SQL_ERROR) { + ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute"); } + + ast_log(LOG_WARNING, "SQL Execute error %d!\n", res); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + stmt = NULL; } return stmt; @@ -468,7 +489,7 @@ static int load_odbc_config(void) struct ast_variable *v; char *cat; const char *dsn, *username, *password, *sanitysql; - int enabled, bse, conntimeout, forcecommit, isolation; + int enabled, bse, conntimeout, forcecommit, isolation, maxconnections; struct timeval ncache = { 0, 0 }; int preconnect = 0, res = 0; struct ast_flags config_flags = { 0 }; @@ -495,6 +516,7 @@ static int load_odbc_config(void) conntimeout = 10; forcecommit = 0; isolation = SQL_TXN_READ_COMMITTED; + maxconnections = 1; for (v = ast_variable_browse(config, cat); v; v = v->next) { if (!strcasecmp(v->name, "pooling") || !strncasecmp(v->name, "share", 5) || @@ -538,6 +560,11 @@ static int load_odbc_config(void) ast_log(LOG_ERROR, "Unrecognized value for 'isolation': '%s' in section '%s'\n", v->value, cat); isolation = SQL_TXN_READ_COMMITTED; } + } else if (!strcasecmp(v->name, "max_connections")) { + if (sscanf(v->value, "%30d", &maxconnections) != 1 || maxconnections < 1) { + ast_log(LOG_WARNING, "max_connections must be a positive integer\n"); + maxconnections = 1; + } } } @@ -563,6 +590,7 @@ static int load_odbc_config(void) new->isolation = isolation; new->conntimeout = conntimeout; new->negative_connection_cache = ncache; + new->maxconnections = maxconnections; if (cat) ast_copy_string(new->name, cat, sizeof(new->name)); @@ -581,6 +609,9 @@ static int load_odbc_config(void) break; } + ast_mutex_init(&new->lock); + ast_cond_init(&new->cond, NULL); + odbc_register_class(new, preconnect); ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); ao2_ref(new, -1); @@ -641,6 +672,7 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm); ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn); ast_cli(a->fd, " Last connection attempt: %s\n", timestr); + ast_cli(a->fd, " Number of active connections: %zd (out of %d)\n", class->connection_cnt, class->maxconnections); ast_cli(a->fd, "\n"); } ao2_ref(class, -1); @@ -654,38 +686,47 @@ static struct ast_cli_entry cli_odbc[] = { AST_CLI_DEFINE(handle_cli_odbc_show, "List ODBC DSN(s)") }; -static int odbc_register_class(struct odbc_class *class, int preconnect) +static void odbc_register_class(struct odbc_class *class, int preconnect) { struct odbc_obj *obj; - if (class) { - ao2_link(class_container, class); - /* I still have a reference in the caller, so a deref is NOT missing here. */ - - if (preconnect) { - /* Request and release builds a connection */ - obj = ast_odbc_request_obj(class->name, 0); - if (obj) { - ast_odbc_release_obj(obj); - } - } - return 0; - } else { - ast_log(LOG_WARNING, "Attempted to register a NULL class?\n"); - return -1; + ao2_link(class_container, class); + /* I still have a reference in the caller, so a deref is NOT missing here. */ + + if (!preconnect) { + return; } + + /* Request and release builds a connection */ + obj = ast_odbc_request_obj(class->name, 0); + if (obj) { + ast_odbc_release_obj(obj); + } + + return; } void ast_odbc_release_obj(struct odbc_obj *obj) { - ast_debug(2, "Releasing ODBC handle %p\n", obj); + struct odbc_class *class = obj->parent; -#ifdef DEBUG_THREADS - obj->file[0] = '\0'; - obj->function[0] = '\0'; - obj->lineno = 0; -#endif - ao2_ref(obj, -1); + ast_debug(2, "Releasing ODBC handle %p into pool\n", obj); + + /* The odbc_obj only holds a reference to the class when it is + * actively being used. This guarantees no circular reference + * between odbc_class and odbc_obj. Since it is being released + * we also release our class reference. If a reload occurred before + * the class will go away automatically once all odbc_obj are + * released back. + */ + obj->parent = NULL; + + ast_mutex_lock(&class->lock); + AST_LIST_INSERT_HEAD(&class->connections, obj, list); + ast_cond_signal(&class->cond); + ast_mutex_unlock(&class->lock); + + ao2_ref(class, -1); } int ast_odbc_backslash_is_escape(struct odbc_obj *obj) @@ -703,6 +744,50 @@ static int aoro2_class_cb(void *obj, void *arg, int flags) return 0; } +/* + * \brief Determine if the connection has died. + * + * \param connection The connection to check + * \param class The ODBC class + * \retval 1 Yep, it's dead + * \retval 0 It's alive and well + */ +static int connection_dead(struct odbc_obj *connection, struct odbc_class *class) +{ + char *test_sql = "select 1"; + SQLINTEGER dead; + SQLRETURN res; + SQLHSTMT stmt; + + res = SQLGetConnectAttr(connection->con, SQL_ATTR_CONNECTION_DEAD, &dead, 0, 0); + if (SQL_SUCCEEDED(res)) { + return dead == SQL_CD_TRUE ? 1 : 0; + } + + /* If the Driver doesn't support SQL_ATTR_CONNECTION_DEAD do a + * probing query instead + */ + res = SQLAllocHandle(SQL_HANDLE_STMT, connection->con, &stmt); + if (!SQL_SUCCEEDED(res)) { + return 1; + } + + if (!ast_strlen_zero(class->sanitysql)) { + test_sql = class->sanitysql; + } + + res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS); + if (!SQL_SUCCEEDED(res)) { + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return 1; + } + + res = SQLExecute(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + + return SQL_SUCCEEDED(res) ? 0 : 1; +} + struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno) { struct odbc_obj *obj = NULL; @@ -713,17 +798,60 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags return NULL; } - /* XXX ODBC connection objects do not have shared ownership, so there is no reason - * to use refcounted objects here. - */ - obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); - /* Inherit reference from the ao2_callback from before */ - obj->parent = class; - if (odbc_obj_connect(obj) == ODBC_FAIL) { - ao2_ref(obj, -1); - return NULL; + ast_mutex_lock(&class->lock); + + while (!obj) { + obj = AST_LIST_REMOVE_HEAD(&class->connections, list); + + if (!obj) { + if (class->connection_cnt < class->maxconnections) { + /* If no connection is immediately available establish a new + * one if allowed. If we try and fail we give up completely as + * we could go into an infinite loop otherwise. + */ + obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); + if (!obj) { + break; + } + + obj->parent = ao2_bump(class); + if (odbc_obj_connect(obj) == ODBC_FAIL) { + ao2_ref(obj->parent, -1); + ao2_ref(obj, -1); + obj = NULL; + break; + } + + class->connection_cnt++; + ast_debug(2, "Created ODBC handle %p on class '%s', new count is %zd\n", obj, + name, class->connection_cnt); + } else { + /* Otherwise if we're not allowed to create a new one we + * wait for another thread to give up the connection they + * own. + */ + ast_cond_wait(&class->cond, &class->lock); + } + } else if (connection_dead(obj, class)) { + /* If the connection is dead try to grab another functional one from the + * pool instead of trying to resurrect this one. + */ + ao2_ref(obj, -1); + obj = NULL; + class->connection_cnt--; + ast_debug(2, "ODBC handle %p dead - removing from class '%s', new count is %zd\n", + obj, name, class->connection_cnt); + } else { + /* We successfully grabbed a connection from the pool and all is well! + */ + obj->parent = ao2_bump(class); + ast_debug(2, "Reusing ODBC handle %p from class '%s'\n", obj, name); + } } + ast_mutex_unlock(&class->lock); + ao2_ref(class, -1); + return obj; } @@ -755,14 +883,6 @@ static odbc_status odbc_obj_disconnect(struct odbc_obj *obj) obj->con = NULL; res = SQLDisconnect(con); - if (obj->parent) { - if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) { - ast_debug(3, "Disconnected %d from %s [%s](%p)\n", res, obj->parent->name, obj->parent->dsn, obj); - } else { - ast_debug(3, "res_odbc: %s [%s](%p) already disconnected\n", obj->parent->name, obj->parent->dsn, obj); - } - } - if ((res = SQLFreeHandle(SQL_HANDLE_DBC, con)) == SQL_SUCCESS) { ast_debug(3, "Database handle %p (connection %p) deallocated\n", obj, con); } else { diff --git a/res/res_phoneprov.c b/res/res_phoneprov.c index df93c5bbc..71f875753 100644 --- a/res/res_phoneprov.c +++ b/res/res_phoneprov.c @@ -410,10 +410,13 @@ static int load_file(const char *filename, char **ret) fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); - if (!(*ret = ast_malloc(len + 1))) + if (!(*ret = ast_malloc(len + 1))) { + fclose(f); return -2; + } if (len != fread(*ret, sizeof(char), len, f)) { + fclose(f); free(*ret); *ret = NULL; return -3; diff --git a/res/res_pjproject.c b/res/res_pjproject.c index f54c3713e..08699f3ee 100644 --- a/res/res_pjproject.c +++ b/res/res_pjproject.c @@ -177,7 +177,6 @@ static void log_forwarder(int level, const char *data, int len) const char * log_source = "pjproject"; int log_line = 0; const char *log_func = "<?>"; - int mod_level; if (pjproject_log_intercept.fd != -1 && pjproject_log_intercept.thread == pthread_self()) { @@ -196,10 +195,8 @@ static void log_forwarder(int level, const char *data, int len) } if (ast_level == __LOG_DEBUG) { - /* For levels 3 and up, obey the debug level for res_pjproject */ - mod_level = ast_opt_dbg_module ? - ast_debug_get_by_module("res_pjproject") : 0; - if (option_debug < level && mod_level < level) { + /* Obey the debug level for res_pjproject */ + if (!DEBUG_ATLEAST(level)) { return; } } diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 97ab8e09e..4468975c9 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -3416,7 +3416,7 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, ast_debug(2, "%p: Set timer to %d msec\n", req_wrapper, timeout); pj_timer_entry_init(req_wrapper->timeout_timer, TIMEOUT_TIMER2, - req_wrapper, &send_request_timer_callback); + req_wrapper, send_request_timer_callback); pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(endpt), req_wrapper->timeout_timer, TIMER_INACTIVE); @@ -3425,8 +3425,18 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, * timer callback is executed. */ ao2_ref(req_wrapper, +1); - pj_timer_heap_schedule(pjsip_endpt_get_timer_heap(endpt), + ret_val = pj_timer_heap_schedule(pjsip_endpt_get_timer_heap(endpt), req_wrapper->timeout_timer, &timeout_timer_val); + if (ret_val != PJ_SUCCESS) { + ao2_unlock(req_wrapper); + ast_log(LOG_ERROR, + "Failed to set timer. Not sending %.*s request to endpoint %s.\n", + (int) pj_strlen(&tdata->msg->line.req.method.name), + pj_strbuf(&tdata->msg->line.req.method.name), + endpoint ? ast_sorcery_object_get_id(endpoint) : "<unknown>"); + ao2_t_ref(req_wrapper, -2, "Drop timer and routine ref"); + return ret_val; + } req_wrapper->timeout_timer->id = TIMEOUT_TIMER2; } else { diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index 1b7850f5f..9c08ccee6 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -25,6 +25,7 @@ #include "asterisk/astobj2.h" #include "asterisk/paths.h" #include "asterisk/sorcery.h" +#include "asterisk/taskprocessor.h" #include "include/res_pjsip_private.h" #include "asterisk/res_pjsip_cli.h" #include "asterisk/statsd.h" @@ -1124,6 +1125,8 @@ int ast_sip_initialize_sorcery_location(void) ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size); ast_sorcery_apply_default(sorcery, "contact", "astdb", "registrar"); + ast_sorcery_object_set_congestion_levels(sorcery, "contact", -1, + 3 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL); ast_sorcery_apply_default(sorcery, "aor", "config", "pjsip.conf,criteria=type=aor"); if (ast_sorcery_object_register(sorcery, "contact", contact_alloc, NULL, contact_apply_handler) || @@ -1140,7 +1143,7 @@ int ast_sip_initialize_sorcery_location(void) ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400); ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout)); - ast_sorcery_object_field_register(sorcery, "contact", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_contact, authenticate_qualify)); + ast_sorcery_object_field_register(sorcery, "contact", "authenticate_qualify", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_contact, authenticate_qualify)); ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy)); ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent)); ast_sorcery_object_field_register(sorcery, "contact", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, endpoint_name)); diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c index 0d3df06f0..e8ed89361 100644 --- a/res/res_pjsip/pjsip_distributor.c +++ b/res/res_pjsip/pjsip_distributor.c @@ -59,6 +59,12 @@ struct unidentified_request{ char src_name[]; }; +/*! Number of serializers in pool if one not otherwise known. (Best if prime number) */ +#define DISTRIBUTOR_POOL_SIZE 31 + +/*! Pool of serializers to use if not supplied. */ +static struct ast_taskprocessor *distributor_pool[DISTRIBUTOR_POOL_SIZE]; + /*! * \internal * \brief Record the task's serializer name on the tdata structure. @@ -278,6 +284,83 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) return dlg; } +/*! + * \internal + * \brief Compute a hash value on a pjlib string + * \since 13.10.0 + * + * \param[in] str The pjlib string to add to the hash + * \param[in] hash The hash value to add to + * + * \details + * This version of the function is for when you need to compute a + * string hash of more than one string. + * + * This famous hash algorithm was written by Dan Bernstein and is + * commonly used. + * + * \sa http://www.cse.yorku.ca/~oz/hash.html + */ +static int pjstr_hash_add(pj_str_t *str, int hash) +{ + size_t len; + const char *pos; + + len = pj_strlen(str); + pos = pj_strbuf(str); + while (len--) { + hash = hash * 33 ^ *pos++; + } + + return hash; +} + +/*! + * \internal + * \brief Compute a hash value on a pjlib string + * \since 13.10.0 + * + * \param[in] str The pjlib string to hash + * + * This famous hash algorithm was written by Dan Bernstein and is + * commonly used. + * + * http://www.cse.yorku.ca/~oz/hash.html + */ +static int pjstr_hash(pj_str_t *str) +{ + return pjstr_hash_add(str, 5381); +} + +struct ast_taskprocessor *ast_sip_get_distributor_serializer(pjsip_rx_data *rdata) +{ + int hash; + pj_str_t *remote_tag; + struct ast_taskprocessor *serializer; + + if (!rdata->msg_info.msg) { + return NULL; + } + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { + remote_tag = &rdata->msg_info.from->tag; + } else { + remote_tag = &rdata->msg_info.to->tag; + } + + /* Compute the hash from the SIP message call-id and remote-tag */ + hash = pjstr_hash(&rdata->msg_info.cid->id); + hash = pjstr_hash_add(remote_tag, hash); + hash = abs(hash); + + serializer = ao2_bump(distributor_pool[hash % ARRAY_LEN(distributor_pool)]); + if (serializer) { + ast_debug(3, "Calculated serializer %s to use for %s\n", + ast_taskprocessor_name(serializer), pjsip_rx_data_get_info(rdata)); + } + return serializer; +} + static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata); static pjsip_module endpoint_mod = { @@ -286,24 +369,31 @@ static pjsip_module endpoint_mod = { .on_rx_request = endpoint_lookup, }; -#define SIP_MAX_QUEUE (AST_TASKPROCESSOR_HIGH_WATER_LEVEL * 3) - static pj_bool_t distributor(pjsip_rx_data *rdata) { - pjsip_dialog *dlg = find_dialog(rdata); + pjsip_dialog *dlg; struct distributor_dialog_data *dist = NULL; struct ast_taskprocessor *serializer = NULL; pjsip_rx_data *clone; + if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) { + /* + * Ignore everything until we are fully booted. Let the + * peer retransmit messages until we are ready. + */ + return PJ_TRUE; + } + + dlg = find_dialog(rdata); if (dlg) { ast_debug(3, "Searching for serializer on dialog %s for %s\n", - dlg->obj_name, rdata->msg_info.info); + dlg->obj_name, pjsip_rx_data_get_info(rdata)); dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); if (dist) { serializer = ao2_bump(dist->serializer); if (serializer) { ast_debug(3, "Found serializer %s on dialog %s\n", - ast_taskprocessor_name(serializer), dlg->obj_name); + ast_taskprocessor_name(serializer), dlg->obj_name); } } pjsip_dlg_dec_lock(dlg); @@ -313,14 +403,47 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) /* We have a serializer so we know where to send the message. */ } else if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG) { ast_debug(3, "No dialog serializer for response %s. Using request transaction as basis\n", - rdata->msg_info.info); + pjsip_rx_data_get_info(rdata)); serializer = find_request_serializer(rdata); + if (!serializer) { + if (ast_taskprocessor_alert_get()) { + /* We're overloaded, ignore the unmatched response. */ + ast_debug(3, "Taskprocessor overload alert: Ignoring unmatched '%s'.\n", + pjsip_rx_data_get_info(rdata)); + return PJ_TRUE; + } + + /* + * Pick a serializer for the unmatched response. Maybe + * the stack can figure out what it is for, or we really + * should just toss it regardless. + */ + serializer = ast_sip_get_distributor_serializer(rdata); + } } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) || !pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_bye_method)) { /* We have a BYE or CANCEL request without a serializer. */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, NULL, NULL, NULL); return PJ_TRUE; + } else { + if (ast_taskprocessor_alert_get()) { + /* + * When taskprocessors get backed up, there is a good chance that + * we are being overloaded and need to defer adding new work to + * the system. To defer the work we will ignore the request and + * rely on the peer's transport layer to retransmit the message. + * We usually work off the overload within a few seconds. The + * alternative is to send back a 503 response to these requests + * and be done with it. + */ + ast_debug(3, "Taskprocessor overload alert: Ignoring '%s'.\n", + pjsip_rx_data_get_info(rdata)); + return PJ_TRUE; + } + + /* Pick a serializer for the out-of-dialog request. */ + serializer = ast_sip_get_distributor_serializer(rdata); } pjsip_rx_data_clone(rdata, 0, &clone); @@ -329,18 +452,9 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) clone->endpt_info.mod_data[endpoint_mod.id] = ao2_bump(dist->endpoint); } - if (ast_sip_threadpool_queue_size() > SIP_MAX_QUEUE) { - /* When the threadpool is backed up this much, there is a good chance that we have encountered - * some sort of terrible condition and don't need to be adding more work to the threadpool. - * It's in our best interest to send back a 503 response and be done with it. - */ - if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, NULL, NULL); - } + if (ast_sip_push_task(serializer, distribute, clone)) { ao2_cleanup(clone->endpt_info.mod_data[endpoint_mod.id]); pjsip_rx_data_free_cloned(clone); - } else { - ast_sip_push_task(serializer, distribute, clone); } ast_taskprocessor_unreference(serializer); @@ -787,6 +901,7 @@ static int cli_unid_print_header(void *obj, void *arg, int flags) return 0; } + static int cli_unid_print_body(void *obj, void *arg, int flags) { struct unidentified_request *unid = obj; @@ -877,6 +992,47 @@ static struct ast_sorcery_observer global_observer = { .loaded = global_loaded, }; +/*! + * \internal + * \brief Shutdown the serializers in the distributor pool. + * \since 13.10.0 + * + * \return Nothing + */ +static void distributor_pool_shutdown(void) +{ + int idx; + + for (idx = 0; idx < ARRAY_LEN(distributor_pool); ++idx) { + ast_taskprocessor_unreference(distributor_pool[idx]); + distributor_pool[idx] = NULL; + } +} + +/*! + * \internal + * \brief Setup the serializers in the distributor pool. + * \since 13.10.0 + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int distributor_pool_setup(void) +{ + char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; + int idx; + + for (idx = 0; idx < ARRAY_LEN(distributor_pool); ++idx) { + /* Create name with seq number appended. */ + ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/distributor"); + + distributor_pool[idx] = ast_sip_create_serializer_named(tps_name); + if (!distributor_pool[idx]) { + return -1; + } + } + return 0; +} int ast_sip_initialize_distributor(void) { @@ -886,6 +1042,11 @@ int ast_sip_initialize_distributor(void) return -1; } + if (distributor_pool_setup()) { + ast_sip_destroy_distributor(); + return -1; + } + prune_context = ast_sched_context_create(); if (!prune_context) { ast_sip_destroy_distributor(); @@ -918,8 +1079,10 @@ int ast_sip_initialize_distributor(void) return -1; } - unid_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL); + unid_formatter = ao2_alloc_options(sizeof(struct ast_sip_cli_formatter_entry), NULL, + AO2_ALLOC_OPT_LOCK_NOLOCK); if (!unid_formatter) { + ast_sip_destroy_distributor(); ast_log(LOG_ERROR, "Unable to allocate memory for unid_formatter\n"); return -1; } @@ -931,6 +1094,7 @@ int ast_sip_initialize_distributor(void) unid_formatter->get_id = cli_unid_get_id; unid_formatter->retrieve_by_id = cli_unid_retrieve_by_id; ast_sip_register_cli_formatter(unid_formatter); + ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands)); return 0; @@ -941,17 +1105,20 @@ void ast_sip_destroy_distributor(void) ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); ast_sip_unregister_cli_formatter(unid_formatter); - internal_sip_unregister_service(&distributor_mod); - internal_sip_unregister_service(&endpoint_mod); internal_sip_unregister_service(&auth_mod); + internal_sip_unregister_service(&endpoint_mod); + internal_sip_unregister_service(&distributor_mod); ao2_cleanup(artificial_auth); ao2_cleanup(artificial_endpoint); - ao2_cleanup(unidentified_requests); ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer); if (prune_context) { ast_sched_context_destroy(prune_context); } + + distributor_pool_shutdown(); + + ao2_cleanup(unidentified_requests); } diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c index ede0d5eba..e220f90e4 100644 --- a/res/res_pjsip/pjsip_options.c +++ b/res/res_pjsip/pjsip_options.c @@ -31,6 +31,7 @@ #include "asterisk/test.h" #include "asterisk/statsd.h" #include "include/res_pjsip_private.h" +#include "asterisk/taskprocessor.h" #define DEFAULT_LANGUAGE "en" #define DEFAULT_ENCODING "text/plain" @@ -1020,6 +1021,8 @@ int ast_sip_initialize_sorcery_qualify(void) /* initialize sorcery ast_sip_contact_status resource */ ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL); + ast_sorcery_object_set_congestion_levels(sorcery, CONTACT_STATUS, -1, + 3 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL); if (ast_sorcery_internal_object_register(sorcery, CONTACT_STATUS, contact_status_alloc, NULL, NULL)) { diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c index 596223293..594c0fdac 100644 --- a/res/res_pjsip_messaging.c +++ b/res/res_pjsip_messaging.c @@ -476,6 +476,24 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf) - 1, 1); res |= ast_msg_set_var(msg, "PJSIP_RECVADDR", field); + switch (rdata->tp_info.transport->key.type) { + case PJSIP_TRANSPORT_UDP: + case PJSIP_TRANSPORT_UDP6: + field = "udp"; + break; + case PJSIP_TRANSPORT_TCP: + case PJSIP_TRANSPORT_TCP6: + field = "tcp"; + break; + case PJSIP_TRANSPORT_TLS: + case PJSIP_TRANSPORT_TLS6: + field = "tls"; + break; + default: + field = rdata->tp_info.transport->type_name; + } + ast_msg_set_var(msg, "PJSIP_TRANSPORT", field); + if (print_body(rdata, buf, sizeof(buf) - 1) > 0) { res |= ast_msg_set_body(msg, "%s", buf); } diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 7ed804acf..65c92c72f 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -355,7 +355,7 @@ struct ast_sip_publication { struct subscription_persistence { /*! Sorcery object details */ SORCERY_OBJECT(details); - /*! The name of the endpoint involved in the subscrption */ + /*! The name of the endpoint involved in the subscription */ char *endpoint; /*! SIP message that creates the subscription */ char packet[PJSIP_MAX_PKT_LEN]; @@ -378,6 +378,20 @@ struct subscription_persistence { }; /*! + * \brief The state of the subscription tree + */ +enum sip_subscription_tree_state { + /*! Normal operation */ + SIP_SUB_TREE_NORMAL = 0, + /*! A terminate has been requested by Asterisk, the client, or pjproject */ + SIP_SUB_TREE_TERMINATE_PENDING, + /*! The terminate is in progress */ + SIP_SUB_TREE_TERMINATE_IN_PROGRESS, + /*! The terminate process has finished and the subscription tree is no longer valid */ + SIP_SUB_TREE_TERMINATED, +}; + +/*! * \brief A tree of SIP subscriptions * * Because of the ability to subscribe to resource lists, a SIP @@ -411,8 +425,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; + /*! Subscription tree state */ + enum sip_subscription_tree_state state; }; /*! @@ -879,15 +893,15 @@ static void build_node_children(struct ast_sip_endpoint *endpoint, const struct "allocation error afterwards\n", resource); continue; } - ast_debug(1, "Subscription to leaf resource %s resulted in success. Adding to parent %s\n", + ast_debug(2, "Subscription to leaf resource %s resulted in success. Adding to parent %s\n", resource, parent->resource); AST_VECTOR_APPEND(&parent->children, current); } else { - ast_debug(1, "Subscription to leaf resource %s resulted in error response %d\n", + ast_debug(2, "Subscription to leaf resource %s resulted in error response %d\n", resource, resp); } } else { - ast_debug(1, "Resource %s (child of %s) is a list\n", resource, parent->resource); + ast_debug(2, "Resource %s (child of %s) is a list\n", resource, parent->resource); current = tree_node_alloc(resource, visited, child_list->full_state); if (!current) { ast_debug(1, "Cannot build children of resource %s due to allocation failure\n", resource); @@ -898,7 +912,7 @@ static void build_node_children(struct ast_sip_endpoint *endpoint, const struct ast_debug(1, "List %s had no successful children.\n", resource); AST_VECTOR_APPEND(&parent->children, current); } else { - ast_debug(1, "List %s had successful children. Adding to parent %s\n", + ast_debug(2, "List %s had successful children. Adding to parent %s\n", resource, parent->resource); tree_node_destroy(current); } @@ -970,7 +984,7 @@ static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct a struct resources visited; if (!has_eventlist_support || !(list = retrieve_resource_list(resource, handler->event_name))) { - ast_debug(1, "Subscription to resource %s is not to a list\n", resource); + ast_debug(2, "Subscription to resource %s is not to a list\n", resource); tree->root = tree_node_alloc(resource, NULL, 0); if (!tree->root) { return 500; @@ -978,7 +992,7 @@ static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct a return handler->notifier->new_subscribe(endpoint, resource); } - ast_debug(1, "Subscription to resource %s is a list\n", resource); + ast_debug(2, "Subscription to resource %s is a list\n", resource); if (AST_VECTOR_INIT(&visited, AST_VECTOR_SIZE(&list->items))) { return 500; } @@ -1037,7 +1051,7 @@ static void remove_subscription(struct sip_subscription_tree *obj) if (i == obj) { AST_RWLIST_REMOVE_CURRENT(next); if (i->root) { - ast_debug(1, "Removing subscription to resource %s from list of subscriptions\n", + ast_debug(2, "Removing subscription to resource %s from list of subscriptions\n", ast_sip_subscription_get_resource_name(i->root)); } break; @@ -1229,10 +1243,9 @@ static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pj pjsip_dlg_inc_session(dlg, &pubsub_module); } -static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint) +static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata) { struct sip_subscription_tree *sub_tree; - char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; sub_tree = ao2_alloc(sizeof *sub_tree, subscription_tree_destructor); if (!sub_tree) { @@ -1241,11 +1254,24 @@ static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_e ast_module_ref(ast_module_info->self); - /* Create name with seq number appended. */ - ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/pubsub/%s", - ast_sorcery_object_get_id(endpoint)); + if (rdata) { + /* + * We must continue using the serializer that the original + * SUBSCRIBE came in on for the dialog. There may be + * retransmissions already enqueued in the original + * serializer that can result in reentrancy and message + * sequencing problems. + */ + sub_tree->serializer = ast_sip_get_distributor_serializer(rdata); + } else { + char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; + + /* Create name with seq number appended. */ + ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/pubsub/%s", + ast_sorcery_object_get_id(endpoint)); - sub_tree->serializer = ast_sip_create_serializer_named(tps_name); + sub_tree->serializer = ast_sip_create_serializer_named(tps_name); + } if (!sub_tree->serializer) { ao2_ref(sub_tree, -1); return NULL; @@ -1286,7 +1312,7 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s pjsip_dialog *dlg; struct subscription_persistence *persistence; - sub_tree = allocate_subscription_tree(endpoint); + sub_tree = allocate_subscription_tree(endpoint, rdata); if (!sub_tree) { *dlg_status = PJ_ENOMEM; return NULL; @@ -1317,6 +1343,10 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub); subscription_setup_dialog(sub_tree, dlg); +#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK + pjsip_evsub_add_ref(sub_tree->evsub); +#endif + ast_sip_mod_data_set(dlg->pool, dlg->mod_data, pubsub_module.id, MOD_DATA_MSG, pjsip_msg_clone(dlg->pool, rdata->msg_info.msg)); @@ -1335,109 +1365,176 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s static int initial_notify_task(void *obj); static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state); -/*! \brief Callback function to perform the actual recreation of a subscription */ -static int subscription_persistence_recreate(void *obj, void *arg, int flags) +/*! Persistent subscription recreation continuation under distributor serializer data */ +struct persistence_recreate_data { + struct subscription_persistence *persistence; + pjsip_rx_data *rdata; +}; + +/*! + * \internal + * \brief subscription_persistence_recreate continuation under distributor serializer. + * \since 13.10.0 + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int sub_persistence_recreate(void *obj) { - struct subscription_persistence *persistence = obj; - pj_pool_t *pool = arg; - pjsip_rx_data rdata = { { 0, }, }; - RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + struct persistence_recreate_data *recreate_data = obj; + struct subscription_persistence *persistence = recreate_data->persistence; + pjsip_rx_data *rdata = recreate_data->rdata; + struct ast_sip_endpoint *endpoint; struct sip_subscription_tree *sub_tree; struct ast_sip_pubsub_body_generator *generator; - int resp; + struct ast_sip_subscription_handler *handler; char *resource; - size_t resource_size; pjsip_sip_uri *request_uri; + size_t resource_size; + int resp; struct resource_tree tree; pjsip_expires_hdr *expires_header; - struct ast_sip_subscription_handler *handler; - /* If this subscription has already expired remove it */ - if (ast_tvdiff_ms(persistence->expires, ast_tvnow()) <= 0) { - ast_sorcery_delete(ast_sip_get_sorcery(), persistence); - return 0; - } + request_uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri); + resource_size = pj_strlen(&request_uri->user) + 1; + resource = ast_alloca(resource_size); + ast_copy_pj_str(resource, &request_uri->user, resource_size); - endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", persistence->endpoint); - if (!endpoint) { - ast_log(LOG_WARNING, "A subscription for '%s' could not be recreated as the endpoint was not found\n", + handler = subscription_get_handler_from_rdata(rdata); + if (!handler || !handler->notifier) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get subscription handler.\n", persistence->endpoint); ast_sorcery_delete(ast_sip_get_sorcery(), persistence); return 0; } - pj_pool_reset(pool); - rdata.tp_info.pool = pool; - - if (ast_sip_create_rdata(&rdata, persistence->packet, persistence->src_name, persistence->src_port, - persistence->transport_key, persistence->local_name, persistence->local_port)) { - ast_log(LOG_WARNING, "A subscription for '%s' could not be recreated as the message could not be parsed\n", + generator = subscription_get_generator_from_rdata(rdata, handler); + if (!generator) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Body generator not available.\n", persistence->endpoint); ast_sorcery_delete(ast_sip_get_sorcery(), persistence); return 0; } - if (rdata.msg_info.msg->type != PJSIP_REQUEST_MSG) { - ast_log(LOG_NOTICE, "Endpoint %s persisted a SIP response instead of a subscribe request. Unable to reload subscription.\n", - ast_sorcery_object_get_id(endpoint)); + ast_sip_mod_data_set(rdata->tp_info.pool, rdata->endpt_info.mod_data, + pubsub_module.id, MOD_DATA_PERSISTENCE, persistence); + + /* Getting the endpoint may take some time that can affect the expiration. */ + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", + persistence->endpoint); + if (!endpoint) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The endpoint was not found\n", + persistence->endpoint); ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + ao2_ref(endpoint, -1); return 0; } - request_uri = pjsip_uri_get_uri(rdata.msg_info.msg->line.req.uri); - resource_size = pj_strlen(&request_uri->user) + 1; - resource = ast_alloca(resource_size); - ast_copy_pj_str(resource, &request_uri->user, resource_size); - /* Update the expiration header with the new expiration */ - expires_header = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, rdata.msg_info.msg->hdr.next); + expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, + rdata->msg_info.msg->hdr.next); if (!expires_header) { - expires_header = pjsip_expires_hdr_create(pool, 0); + expires_header = pjsip_expires_hdr_create(rdata->tp_info.pool, 0); if (!expires_header) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not update expires header.\n", + persistence->endpoint); ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + ao2_ref(endpoint, -1); return 0; } - pjsip_msg_add_hdr(rdata.msg_info.msg, (pjsip_hdr*)expires_header); + pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr *) expires_header); } expires_header->ivalue = (ast_tvdiff_ms(persistence->expires, ast_tvnow()) / 1000); - - handler = subscription_get_handler_from_rdata(&rdata); - if (!handler || !handler->notifier) { - ast_sorcery_delete(ast_sip_get_sorcery(), persistence); - return 0; - } - - generator = subscription_get_generator_from_rdata(&rdata, handler); - if (!generator) { + if (expires_header->ivalue <= 0) { + /* The subscription expired since we started recreating the subscription. */ ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + ao2_ref(endpoint, -1); return 0; } - ast_sip_mod_data_set(rdata.tp_info.pool, rdata.endpt_info.mod_data, - pubsub_module.id, MOD_DATA_PERSISTENCE, persistence); - memset(&tree, 0, sizeof(tree)); resp = build_resource_tree(endpoint, handler, resource, &tree, - ast_sip_pubsub_has_eventlist_support(&rdata)); + ast_sip_pubsub_has_eventlist_support(rdata)); if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) { pj_status_t dlg_status; - sub_tree = create_subscription_tree(handler, endpoint, &rdata, resource, generator, &tree, &dlg_status); + sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, + &tree, &dlg_status); if (!sub_tree) { - ast_sorcery_delete(ast_sip_get_sorcery(), persistence); - ast_log(LOG_WARNING, "Failed to re-create subscription for %s\n", persistence->endpoint); - return 0; - } - sub_tree->persistence = ao2_bump(persistence); - subscription_persistence_update(sub_tree, &rdata); - if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ao2_bump(sub_tree))) { - pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE); - ao2_ref(sub_tree, -1); + if (dlg_status != PJ_EEXISTS) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not create subscription tree.\n", + persistence->endpoint); + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + } + } else { + sub_tree->persistence = ao2_bump(persistence); + subscription_persistence_update(sub_tree, rdata); + if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, + ao2_bump(sub_tree))) { + /* Could not send initial subscribe NOTIFY */ + pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE); + ao2_ref(sub_tree, -1); + } } } else { ast_sorcery_delete(ast_sip_get_sorcery(), persistence); } resource_tree_destroy(&tree); + ao2_ref(endpoint, -1); + + return 0; +} + +/*! \brief Callback function to perform the actual recreation of a subscription */ +static int subscription_persistence_recreate(void *obj, void *arg, int flags) +{ + struct subscription_persistence *persistence = obj; + pj_pool_t *pool = arg; + struct ast_taskprocessor *serializer; + pjsip_rx_data rdata; + struct persistence_recreate_data recreate_data; + + /* If this subscription has already expired remove it */ + if (ast_tvdiff_ms(persistence->expires, ast_tvnow()) <= 0) { + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + return 0; + } + + memset(&rdata, 0, sizeof(rdata)); + pj_pool_reset(pool); + rdata.tp_info.pool = pool; + + if (ast_sip_create_rdata(&rdata, persistence->packet, persistence->src_name, persistence->src_port, + persistence->transport_key, persistence->local_name, persistence->local_port)) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The message could not be parsed\n", + persistence->endpoint); + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + return 0; + } + + if (rdata.msg_info.msg->type != PJSIP_REQUEST_MSG) { + ast_log(LOG_NOTICE, "Failed recreating '%s' subscription: Stored a SIP response instead of a request.\n", + persistence->endpoint); + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + return 0; + } + + /* Continue the remainder in the distributor serializer */ + serializer = ast_sip_get_distributor_serializer(&rdata); + if (!serializer) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get distributor serializer.\n", + persistence->endpoint); + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + return 0; + } + recreate_data.persistence = persistence; + recreate_data.rdata = &rdata; + if (ast_sip_push_task_synchronous(serializer, sub_persistence_recreate, &recreate_data)) { + ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not continue under distributor serializer.\n", + persistence->endpoint); + ast_sorcery_delete(ast_sip_get_sorcery(), persistence); + } + ast_taskprocessor_unreference(serializer); return 0; } @@ -1593,7 +1690,7 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su pjsip_evsub *evsub; struct sip_subscription_tree *sub_tree = NULL; - sub_tree = allocate_subscription_tree(endpoint); + sub_tree = allocate_subscription_tree(endpoint, NULL); if (!sub_tree) { return NULL; } @@ -2173,10 +2270,8 @@ 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)) { + pjsip_tx_data_dec_ref(tdata); return -1; } @@ -2191,21 +2286,32 @@ static int serialized_send_notify(void *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 + * and now a new SUBSCRIBE arrived requiring full state to be + * sent out in an immediate NOTIFY. It's also possible that we're + * already processing a terminate. If that has happened, we need to * bail out here instead of sending the batched NOTIFY. */ - if (!sub_tree->send_scheduled_notify) { + + if (sub_tree->state >= SIP_SUB_TREE_TERMINATE_IN_PROGRESS + || !sub_tree->send_scheduled_notify) { pjsip_dlg_dec_lock(dlg); ao2_cleanup(sub_tree); return 0; } + if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) { + sub_tree->state = SIP_SUB_TREE_TERMINATE_IN_PROGRESS; + } + send_notify(sub_tree, 0); - ast_test_suite_event_notify("SUBSCRIPTION_STATE_CHANGED", - "Resource: %s", - sub_tree->root->resource); + + ast_test_suite_event_notify( + sub_tree->state == SIP_SUB_TREE_TERMINATED + ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED", + "Resource: %s", sub_tree->root->resource); + sub_tree->notify_sched_id = -1; pjsip_dlg_dec_lock(dlg); ao2_cleanup(sub_tree); @@ -2217,7 +2323,10 @@ static int sched_cb(const void *data) struct sip_subscription_tree *sub_tree = (struct sip_subscription_tree *) data; /* We don't need to bump the refcount of sub_tree since we bumped it when scheduling this task */ - ast_sip_push_task(sub_tree->serializer, serialized_send_notify, sub_tree); + if (ast_sip_push_task(sub_tree->serializer, serialized_send_notify, sub_tree)) { + ao2_cleanup(sub_tree); + } + return 0; } @@ -2228,12 +2337,13 @@ static int schedule_notification(struct sip_subscription_tree *sub_tree) return 0; } + sub_tree->send_scheduled_notify = 1; sub_tree->notify_sched_id = ast_sched_add(sched, sub_tree->notification_batch_interval, sched_cb, ao2_bump(sub_tree)); if (sub_tree->notify_sched_id < 0) { + ao2_cleanup(sub_tree); return -1; } - sub_tree->send_scheduled_notify = 1; return 0; } @@ -2245,7 +2355,7 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip pjsip_dlg_inc_lock(dlg); - if (!sub->tree->evsub) { + if (sub->tree->state != SIP_SUB_TREE_NORMAL) { pjsip_dlg_dec_lock(dlg); return 0; } @@ -2259,6 +2369,7 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip sub->body_changed = 1; if (terminate) { sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED; + sub->tree->state = SIP_SUB_TREE_TERMINATE_PENDING; } if (sub->tree->notification_batch_interval) { @@ -2266,6 +2377,9 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip } else { /* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */ ao2_ref(sub->tree, +1); + if (terminate) { + sub->tree->state = SIP_SUB_TREE_TERMINATE_IN_PROGRESS; + } res = send_notify(sub->tree, 0); ast_test_suite_event_notify(terminate ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED", "Resource: %s", @@ -3249,71 +3363,72 @@ static void set_state_terminated(struct ast_sip_subscription *sub) } } -/* 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 + * \brief Callback sequence for subscription terminate: * - * 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. + * * Client initiated: + * pjproject receives SUBSCRIBE on the subscription's serializer thread + * calls pubsub_on_rx_refresh with dialog locked + * pubsub_on_rx_refresh sets TERMINATE_PENDING + * pushes serialized_pubsub_on_refresh_timeout + * returns to pjproject + * pjproject calls pubsub_on_evsub_state + * pubsub_evsub_set_state checks state == TERMINATE_IN_PROGRESS (no) + * ignore and return + * pjproject unlocks dialog + * serialized_pubsub_on_refresh_timeout starts (1) + * locks dialog + * checks state == TERMINATE_PENDING + * sets TERMINATE_IN_PROGRESS + * calls send_notify (2) + * send_notify ultimately calls pjsip_evsub_send_request + * pjsip_evsub_send_request calls evsub's set_state + * set_state calls pubsub_evsub_set_state + * pubsub_evsub_set_state checks state == TERMINATE_IN_PROGRESS + * removes the subscriptions + * cleans up references to evsub + * sets state = TERMINATED + * serialized_pubsub_on_refresh_timeout unlocks dialog * - * 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. + * * Subscription timer expires: + * pjproject timer expires + * locks dialog + * calls pubsub_on_server_timeout + * pubsub_on_server_timeout checks state == NORMAL + * sets TERMINATE_PENDING + * pushes serialized_pubsub_on_refresh_timeout + * returns to pjproject + * pjproject unlocks dialog + * serialized_pubsub_on_refresh_timeout starts + * See (1) Above * - * 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. + * * ast_sip_subscription_notify is called + * checks state == NORMAL + * if not batched... + * sets TERMINATE_IN_PROGRESS (if terminate is requested) + * calls send_notify + * See (2) Above + * if batched... + * sets TERMINATE_PENDING + * schedules task + * scheduler runs sched_task + * sched_task pushes serialized_send_notify + * serialized_send_notify starts + * checks state <= TERMINATE_PENDING + * if state == TERMINATE_PENDING set state = TERMINATE_IN_PROGRESS + * call send_notify + * See (2) Above * - * 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 + */ + +/*! + * \brief PJSIP callback when underlying SIP subscription changes state * - * \li The subscription tree's last_notify field - * \li The subscription tree's evsub pointer + * Although this function is called for every state change, we only care + * about the TERMINATED state, and only when we're actually processing the final + * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS). In this case, we do all + * the subscription tree cleanup tasks and decrement the evsub reference. */ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event) { @@ -3326,51 +3441,55 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event) } sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); - if (!sub_tree) { + if (!sub_tree || sub_tree->state != SIP_SUB_TREE_TERMINATE_IN_PROGRESS) { + ast_debug(1, "Possible terminate race prevented %p\n", sub_tree); return; } - 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; - } - } - remove_subscription(sub_tree); + pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL); + +#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK + pjsip_evsub_dec_ref(sub_tree->evsub); +#endif + 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); + sub_tree->state = SIP_SUB_TREE_TERMINATED; /* Remove evsub's reference to the sub_tree */ ao2_ref(sub_tree, -1); } -static int serialized_pubsub_on_rx_refresh(void *userdata) +static int serialized_pubsub_on_refresh_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) { + if (sub_tree->state >= SIP_SUB_TREE_TERMINATE_IN_PROGRESS) { + ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree->evsub, sub_tree->state); pjsip_dlg_dec_lock(dlg); + ao2_cleanup(sub_tree); return 0; } - if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) { + if (sub_tree->state == SIP_SUB_TREE_TERMINATE_PENDING) { + sub_tree->state = SIP_SUB_TREE_TERMINATE_IN_PROGRESS; 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); + "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED", + "Resource: %s", sub_tree->root->resource); pjsip_dlg_dec_lock(dlg); ao2_cleanup(sub_tree); @@ -3383,10 +3502,8 @@ static int serialized_pubsub_on_rx_refresh(void *userdata) * 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. + * In either case we push serialized_pubsub_on_refresh_timeout to send an + * appropriate NOTIFY request. */ 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) @@ -3394,18 +3511,24 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata, struct sip_subscription_tree *sub_tree; sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); - if (!sub_tree) { + if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) { + ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree, sub_tree ? sub_tree->state : -1 ); return; } /* PJSIP will set the evsub's state to terminated before calling into this function * if the Expires value of the incoming SUBSCRIBE is 0. */ - 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); - } + + if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) { + sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING; + } + + if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_refresh_timeout, ao2_bump(sub_tree))) { + /* If we can't push the NOTIFY refreshing task...we'll just go with it. */ + ast_log(LOG_ERROR, "Failed to push task to send NOTIFY.\n"); + sub_tree->state = SIP_SUB_TREE_NORMAL; + ao2_ref(sub_tree, -1); } if (sub_tree->is_list) { @@ -3416,9 +3539,9 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata, static void pubsub_on_rx_notify(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) { - struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); + struct ast_sip_subscription *sub; - if (!sub) { + if (!(sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) { return; } @@ -3431,45 +3554,62 @@ static int serialized_pubsub_on_client_refresh(void *userdata) struct sip_subscription_tree *sub_tree = userdata; pjsip_tx_data *tdata; + if (!sub_tree->evsub) { + ao2_cleanup(sub_tree); + return 0; + } + if (pjsip_evsub_initiate(sub_tree->evsub, NULL, -1, &tdata) == PJ_SUCCESS) { pjsip_evsub_send_request(sub_tree->evsub, tdata); } else { pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE); } + ao2_cleanup(sub_tree); return 0; } static void pubsub_on_client_refresh(pjsip_evsub *evsub) { - struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); + struct sip_subscription_tree *sub_tree; - ao2_ref(sub_tree, +1); - ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, sub_tree); + if (!(sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) { + return; + } + + if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, ao2_bump(sub_tree))) { + ao2_cleanup(sub_tree); + } } static void pubsub_on_server_timeout(pjsip_evsub *evsub) { + struct sip_subscription_tree *sub_tree; - struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); - if (!sub_tree) { - /* 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. - */ + /* 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. + */ + + sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id); + if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) { + ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree, sub_tree ? sub_tree->state : -1 ); return; } - ao2_ref(sub_tree, +1); - ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_server_timeout, sub_tree); + sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING; + if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_refresh_timeout, ao2_bump(sub_tree))) { + sub_tree->state = SIP_SUB_TREE_NORMAL; + ao2_cleanup(sub_tree); + } } static int ami_subscription_detail(struct sip_subscription_tree *sub_tree, diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index 838f4616d..aef0e164b 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -231,155 +231,11 @@ static void registrar_add_date_header(pjsip_tx_data *tdata) ast_sip_add_header(tdata, "Date", date); } -#define SERIALIZER_BUCKETS 59 - -static struct ao2_container *serializers; - -/*! \brief Serializer with associated aor key */ -struct serializer { - /* Serializer to distribute tasks to */ - struct ast_taskprocessor *serializer; - /* The name of the aor to associate with the serializer */ - char aor_name[0]; -}; - -static void serializer_destroy(void *obj) -{ - struct serializer *ser = obj; - - ast_taskprocessor_unreference(ser->serializer); -} - -static struct serializer *serializer_create(const char *aor_name) -{ - char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; - size_t size = strlen(aor_name) + 1; - struct serializer *ser = ao2_alloc( - sizeof(*ser) + size, serializer_destroy); - - if (!ser) { - return NULL; - } - - /* Create name with seq number appended. */ - ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/aor/%s", - aor_name); - - if (!(ser->serializer = ast_sip_create_serializer_named(tps_name))) { - ao2_ref(ser, -1); - return NULL; - } - - strcpy(ser->aor_name, aor_name); - return ser; -} - -static struct serializer *serializer_find_or_create(const char *aor_name) -{ - struct serializer *ser = ao2_find(serializers, aor_name, OBJ_SEARCH_KEY); - - if (ser) { - return ser; - } - - if (!(ser = serializer_create(aor_name))) { - return NULL; - } - - ao2_link(serializers, ser); - return ser; -} - -static int serializer_hash(const void *obj, const int flags) -{ - const struct serializer *object; - const char *key; - - switch (flags & OBJ_SEARCH_MASK) { - case OBJ_SEARCH_KEY: - key = obj; - return ast_str_hash(key); - case OBJ_SEARCH_OBJECT: - object = obj; - return ast_str_hash(object->aor_name); - default: - /* Hash can only work on something with a full key. */ - ast_assert(0); - return 0; - } -} - -static int serializer_cmp(void *obj_left, void *obj_right, int flags) -{ - const struct serializer *object_left = obj_left; - const struct serializer *object_right = obj_right; - const char *right_key = obj_right; - int cmp; - - switch (flags & OBJ_SEARCH_MASK) { - case OBJ_SEARCH_OBJECT: - right_key = object_right->aor_name; - /* Fall through */ - case OBJ_SEARCH_KEY: - cmp = strcmp(object_left->aor_name, right_key); - break; - case OBJ_SEARCH_PARTIAL_KEY: - /* - * We could also use a partial key struct containing a length - * so strlen() does not get called for every comparison instead. - */ - cmp = strncmp(object_left->aor_name, right_key, strlen(right_key)); - break; - default: - cmp = 0; - break; - } - - return cmp ? 0 : CMP_MATCH; -} - -struct rx_task_data { - pjsip_rx_data *rdata; - struct ast_sip_endpoint *endpoint; - struct ast_sip_aor *aor; -}; - -static void rx_task_data_destroy(void *obj) -{ - struct rx_task_data *task_data = obj; - - pjsip_rx_data_free_cloned(task_data->rdata); - ao2_cleanup(task_data->endpoint); - ao2_cleanup(task_data->aor); -} - -static struct rx_task_data *rx_task_data_create(pjsip_rx_data *rdata, - struct ast_sip_endpoint *endpoint, - struct ast_sip_aor *aor) -{ - struct rx_task_data *task_data = ao2_alloc( - sizeof(*task_data), rx_task_data_destroy); - - if (!task_data) { - return NULL; - } - - pjsip_rx_data_clone(rdata, 0, &task_data->rdata); - - task_data->endpoint = endpoint; - ao2_ref(task_data->endpoint, +1); - - task_data->aor = aor; - ao2_ref(task_data->aor, +1); - - return task_data; -} - static const pj_str_t path_hdr_name = { "Path", 4 }; -static int build_path_data(struct rx_task_data *task_data, struct ast_str **path_str) +static int build_path_data(pjsip_rx_data *rdata, struct ast_str **path_str) { - pjsip_generic_string_hdr *path_hdr = pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &path_hdr_name, NULL); + pjsip_generic_string_hdr *path_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &path_hdr_name, NULL); if (!path_hdr) { return 0; @@ -392,24 +248,24 @@ static int build_path_data(struct rx_task_data *task_data, struct ast_str **path ast_str_set(path_str, 0, "%.*s", (int)path_hdr->hvalue.slen, path_hdr->hvalue.ptr); - while ((path_hdr = (pjsip_generic_string_hdr *) pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &path_hdr_name, path_hdr->next))) { + while ((path_hdr = (pjsip_generic_string_hdr *) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &path_hdr_name, path_hdr->next))) { ast_str_append(path_str, 0, ",%.*s", (int)path_hdr->hvalue.slen, path_hdr->hvalue.ptr); } return 0; } -static int registrar_validate_path(struct rx_task_data *task_data, struct ast_str **path_str) +static int registrar_validate_path(pjsip_rx_data *rdata, struct ast_sip_aor *aor, struct ast_str **path_str) { const pj_str_t path_supported_name = { "path", 4 }; pjsip_supported_hdr *supported_hdr; int i; - if (!task_data->aor->support_path) { + if (!aor->support_path) { return 0; } - if (build_path_data(task_data, path_str)) { + if (build_path_data(rdata, path_str)) { return -1; } @@ -417,7 +273,7 @@ static int registrar_validate_path(struct rx_task_data *task_data, struct ast_st return 0; } - supported_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_SUPPORTED, NULL); + supported_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_SUPPORTED, NULL); if (!supported_hdr) { return -1; } @@ -433,8 +289,11 @@ static int registrar_validate_path(struct rx_task_data *task_data, struct ast_st return -1; } -static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *contacts, - const char *aor_name) +static int register_aor_core(pjsip_rx_data *rdata, + struct ast_sip_endpoint *endpoint, + struct ast_sip_aor *aor, + const char *aor_name, + struct ao2_container *contacts) { static const pj_str_t USER_AGENT = { "User-Agent", 10 }; @@ -458,38 +317,38 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co /* So we don't count static contacts against max_contacts we prune them out from the container */ ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, registrar_prune_static, NULL); - if (registrar_validate_contacts(task_data->rdata, contacts, task_data->aor, &added, &updated, &deleted)) { + if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) { /* The provided Contact headers do not conform to the specification */ - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 400, NULL, NULL, NULL); - ast_sip_report_failed_acl(task_data->endpoint, task_data->rdata, "registrar_invalid_contacts_provided"); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_contacts_provided"); ast_log(LOG_WARNING, "Failed to validate contacts in REGISTER request from '%s'\n", - ast_sorcery_object_get_id(task_data->endpoint)); + ast_sorcery_object_get_id(endpoint)); return PJ_TRUE; } - if (registrar_validate_path(task_data, &path_str)) { + if (registrar_validate_path(rdata, aor, &path_str)) { /* Ensure that intervening proxies did not make invalid modifications to the request */ - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 420, NULL, NULL, NULL); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 420, NULL, NULL, NULL); ast_log(LOG_WARNING, "Invalid modifications made to REGISTER request from '%s' by intervening proxy\n", - ast_sorcery_object_get_id(task_data->endpoint)); + ast_sorcery_object_get_id(endpoint)); return PJ_TRUE; } - if ((MAX(added - deleted, 0) + (!task_data->aor->remove_existing ? ao2_container_count(contacts) : 0)) > task_data->aor->max_contacts) { + if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) { /* Enforce the maximum number of contacts */ - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 403, NULL, NULL, NULL); - ast_sip_report_failed_acl(task_data->endpoint, task_data->rdata, "registrar_attempt_exceeds_maximum_configured_contacts"); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts"); ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' to AOR '%s' will exceed max contacts of %u\n", - ast_sorcery_object_get_id(task_data->endpoint), ast_sorcery_object_get_id(task_data->aor), task_data->aor->max_contacts); + ast_sorcery_object_get_id(endpoint), aor_name, aor->max_contacts); return PJ_TRUE; } if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 500, NULL, NULL, NULL); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); return PJ_TRUE; } - user_agent_hdr = pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &USER_AGENT, NULL); + user_agent_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &USER_AGENT, NULL); if (user_agent_hdr) { alloc_size = pj_strlen(&user_agent_hdr->hvalue) + 1; user_agent = ast_alloca(alloc_size); @@ -497,10 +356,10 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co } /* Find the first Via header */ - via_hdr = via_hdr_last = (pjsip_via_hdr*) pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_VIA, NULL); + via_hdr = via_hdr_last = (pjsip_via_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_VIA, NULL); if (via_hdr) { /* Find the last Via header */ - while ( (via_hdr = (pjsip_via_hdr*) pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, + while ( (via_hdr = (pjsip_via_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_VIA, via_hdr->next)) != NULL) { via_hdr_last = via_hdr; } @@ -510,7 +369,7 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co via_port=via_hdr_last->sent_by.port; } - call_id_hdr = (pjsip_cid_hdr*) pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CALL_ID, NULL); + call_id_hdr = (pjsip_cid_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CALL_ID, NULL); if (call_id_hdr) { alloc_size = pj_strlen(&call_id_hdr->id) + 1; call_id = ast_alloca(alloc_size); @@ -518,7 +377,7 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co } /* Iterate each provided Contact header and add, update, or delete */ - while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) { + while ((contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) { int expiration; char contact_uri[pjsip_max_url_size]; RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); @@ -534,7 +393,7 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co continue; } - expiration = registrar_get_expiration(task_data->aor, contact_hdr, task_data->rdata); + expiration = registrar_get_expiration(aor, contact_hdr, rdata); details.uri = pjsip_uri_get_uri(contact_hdr->uri); pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)); @@ -546,9 +405,9 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co continue; } - if (ast_sip_location_add_contact_nolock(task_data->aor, contact_uri, ast_tvadd(ast_tvnow(), + if (ast_sip_location_add_contact_nolock(aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)), path_str ? ast_str_buffer(path_str) : NULL, - user_agent, via_addr, via_port, call_id, task_data->endpoint)) { + user_agent, via_addr, via_port, call_id, endpoint)) { ast_log(LOG_ERROR, "Unable to bind contact '%s' to AOR '%s'\n", contact_uri, aor_name); continue; @@ -576,8 +435,8 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co } contact_update->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)); - contact_update->qualify_frequency = task_data->aor->qualify_frequency; - contact_update->authenticate_qualify = task_data->aor->authenticate_qualify; + contact_update->qualify_frequency = aor->qualify_frequency; + contact_update->authenticate_qualify = aor->authenticate_qualify; if (path_str) { ast_string_field_set(contact_update, path, ast_str_buffer(path_str)); } @@ -625,16 +484,16 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER * do so */ - if (task_data->aor->remove_existing) { + if (aor->remove_existing) { ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL); } /* Re-retrieve contacts. Caller will clean up the original container. */ - contacts = ast_sip_location_retrieve_aor_contacts_nolock(task_data->aor); + contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor); response_contact = ao2_callback(contacts, 0, NULL, NULL); /* Send a response containing all of the contacts (including static) that are present on this AOR */ - if (ast_sip_create_response(task_data->rdata, 200, response_contact, &tdata) != PJ_SUCCESS) { + if (ast_sip_create_response(rdata, 200, response_contact, &tdata) != PJ_SUCCESS) { ao2_cleanup(response_contact); ao2_cleanup(contacts); return PJ_TRUE; @@ -647,44 +506,42 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co ao2_callback(contacts, 0, registrar_add_contact, tdata); ao2_cleanup(contacts); - if ((expires_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL))) { - expires_hdr = pjsip_expires_hdr_create(tdata->pool, registrar_get_expiration(task_data->aor, NULL, task_data->rdata)); + if ((expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL))) { + expires_hdr = pjsip_expires_hdr_create(tdata->pool, registrar_get_expiration(aor, NULL, rdata)); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires_hdr); } - ast_sip_send_stateful_response(task_data->rdata, tdata, task_data->endpoint); + ast_sip_send_stateful_response(rdata, tdata, endpoint); return PJ_TRUE; } -static int rx_task(void *data) +static int register_aor(pjsip_rx_data *rdata, + struct ast_sip_endpoint *endpoint, + struct ast_sip_aor *aor, + const char *aor_name) { int res; - struct rx_task_data *task_data = data; struct ao2_container *contacts = NULL; struct ast_named_lock *lock; - const char *aor_name = ast_sorcery_object_get_id(task_data->aor); lock = ast_named_lock_get(AST_NAMED_LOCK_TYPE_RWLOCK, "aor", aor_name); if (!lock) { - ao2_cleanup(task_data); return PJ_TRUE; } ao2_wrlock(lock); - contacts = ast_sip_location_retrieve_aor_contacts_nolock(task_data->aor); + contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor); if (!contacts) { ao2_unlock(lock); ast_named_lock_put(lock); - ao2_cleanup(task_data); return PJ_TRUE; } - res = rx_task_core(task_data, contacts, aor_name); + res = register_aor_core(rdata, endpoint, aor, aor_name, contacts); ao2_cleanup(contacts); ao2_unlock(lock); ast_named_lock_put(lock); - ao2_cleanup(task_data); return res; } @@ -748,44 +605,20 @@ static char *find_aor_name(const char *username, const char *domain, const char return NULL; } -static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) +static struct ast_sip_aor *find_registrar_aor(struct pjsip_rx_data *rdata, struct ast_sip_endpoint *endpoint) { - RAII_VAR(struct serializer *, ser, NULL, ao2_cleanup); - struct rx_task_data *task_data; - - RAII_VAR(struct ast_sip_endpoint *, endpoint, - ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); - RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup); - char *domain_name = NULL; + struct ast_sip_aor *aor = NULL; + char *aor_name = NULL; + char *domain_name; char *username = NULL; - RAII_VAR(char *, aor_name, NULL, ast_free); int i; - if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) { - return PJ_FALSE; - } - - if (ast_strlen_zero(endpoint->aors)) { - /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */ - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); - ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors"); - ast_log(LOG_WARNING, "Endpoint '%s' has no configured AORs\n", ast_sorcery_object_get_id(endpoint)); - return PJ_TRUE; - } - - if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL); - ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received"); - ast_log(LOG_WARNING, "Endpoint '%s' attempted to register to an AOR with a non-SIP URI\n", ast_sorcery_object_get_id(endpoint)); - return PJ_TRUE; - } - - for (i = 0; i < AST_VECTOR_SIZE(&endpoint->ident_method_order); i++) { + for (i = 0; i < AST_VECTOR_SIZE(&endpoint->ident_method_order); ++i) { pjsip_sip_uri *uri; pjsip_authorization_hdr *header = NULL; switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) { - case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME : + case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME: uri = pjsip_uri_get_uri(rdata->msg_info.to->uri); domain_name = ast_alloca(uri->host.slen + 1); @@ -798,7 +631,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) ast_debug(3, "Matched aor '%s' by To username\n", aor_name); } break; - case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME : + case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME: while ((header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, header ? header->next : NULL))) { if (header && !pj_stricmp2(&header->scheme, "digest")) { @@ -828,42 +661,57 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) /* The provided AOR name was not found (be it within the configuration or sorcery itself) */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL); ast_sip_report_req_no_support(endpoint, rdata, "registrar_requested_aor_not_found"); - ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", username, ast_sorcery_object_get_id(endpoint)); - return PJ_TRUE; + ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", + username ?: "", ast_sorcery_object_get_id(endpoint)); } + ast_free(aor_name); + return aor; +} - if (!aor->max_contacts) { - /* Registration is not permitted for this AOR */ +static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, + ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); + struct ast_sip_aor *aor; + const char *aor_name; + + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) { + return PJ_FALSE; + } + + if (ast_strlen_zero(endpoint->aors)) { + /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); - ast_sip_report_req_no_support(endpoint, rdata, "registrar_attempt_without_registration_permitted"); - ast_log(LOG_WARNING, "AOR '%s' has no configured max_contacts. Endpoint '%s' unable to register\n", - ast_sorcery_object_get_id(aor), ast_sorcery_object_get_id(endpoint)); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors"); + ast_log(LOG_WARNING, "Endpoint '%s' has no configured AORs\n", ast_sorcery_object_get_id(endpoint)); return PJ_TRUE; } - if (!(ser = serializer_find_or_create(aor_name))) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); - ast_sip_report_mem_limit(endpoint, rdata); - ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not get serializer\n", - ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor)); + if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received"); + ast_log(LOG_WARNING, "Endpoint '%s' attempted to register to an AOR with a non-SIP URI\n", ast_sorcery_object_get_id(endpoint)); return PJ_TRUE; } - if (!(task_data = rx_task_data_create(rdata, endpoint, aor))) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); - ast_sip_report_mem_limit(endpoint, rdata); - ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not create rx_task_data\n", - ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor)); + aor = find_registrar_aor(rdata, endpoint); + if (!aor) { + /* We've already responded about not finding an AOR. */ return PJ_TRUE; } - if (ast_sip_push_task(ser->serializer, rx_task, task_data)) { + aor_name = ast_sorcery_object_get_id(aor); + + if (!aor->max_contacts) { + /* Registration is not permitted for this AOR */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); - ast_sip_report_mem_limit(endpoint, rdata); - ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not serialize task\n", - ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor)); - ao2_ref(task_data, -1); + ast_sip_report_req_no_support(endpoint, rdata, "registrar_attempt_without_registration_permitted"); + ast_log(LOG_WARNING, "AOR '%s' has no configured max_contacts. Endpoint '%s' unable to register\n", + aor_name, ast_sorcery_object_get_id(endpoint)); + } else { + register_aor(rdata, endpoint, aor, aor_name); } + ao2_ref(aor, -1); return PJ_TRUE; } @@ -952,11 +800,6 @@ static int load_module(void) CHECK_PJSIP_MODULE_LOADED(); - if (!(serializers = ao2_container_alloc( - SERIALIZER_BUCKETS, serializer_hash, serializer_cmp))) { - return AST_MODULE_LOAD_DECLINE; - } - if (ast_sip_register_service(®istrar_module)) { return AST_MODULE_LOAD_DECLINE; } @@ -976,8 +819,6 @@ static int unload_module(void) { ast_manager_unregister(AMI_SHOW_REGISTRATIONS); ast_sip_unregister_service(®istrar_module); - - ao2_cleanup(serializers); return 0; } diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index 18a7f3f6a..029eb5d27 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -420,7 +420,7 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format * *++tmp = '\0'; /* ast...generate gives us everything, just need value */ tmp = strchr(ast_str_buffer(fmtp0), ':'); - if (tmp && tmp + 1) { + if (tmp && tmp[1] != '\0') { fmtp1 = pj_str(tmp + 1); } else { fmtp1 = pj_str(ast_str_buffer(fmtp0)); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index d66a819d7..23d2f2f2a 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -1403,12 +1403,11 @@ struct ast_sip_channel_pvt *ast_sip_channel_pvt_alloc(void *pvt, struct ast_sip_ } struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, - struct ast_sip_contact *contact, pjsip_inv_session *inv_session) + struct ast_sip_contact *contact, pjsip_inv_session *inv_session, pjsip_rx_data *rdata) { RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); struct ast_sip_session_supplement *iter; int dsp_features = 0; - char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; session = ao2_alloc(sizeof(*session), session_destructor); if (!session) { @@ -1429,11 +1428,24 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, /* fill session->media with available types */ ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session); - /* Create name with seq number appended. */ - ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/session/%s", - ast_sorcery_object_get_id(endpoint)); + if (rdata) { + /* + * We must continue using the serializer that the original + * INVITE came in on for the dialog. There may be + * retransmissions already enqueued in the original + * serializer that can result in reentrancy and message + * sequencing problems. + */ + session->serializer = ast_sip_get_distributor_serializer(rdata); + } else { + char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1]; + + /* Create name with seq number appended. */ + ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outsess/%s", + ast_sorcery_object_get_id(endpoint)); - session->serializer = ast_sip_create_serializer_named(tps_name); + session->serializer = ast_sip_create_serializer_named(tps_name); + } if (!session->serializer) { return NULL; } @@ -1731,7 +1743,9 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint timer.sess_expires = endpoint->extensions.timer.sess_expires; pjsip_timer_init_session(inv_session, &timer); - if (!(session = ast_sip_session_alloc(endpoint, found_contact ? found_contact : contact, inv_session))) { + session = ast_sip_session_alloc(endpoint, found_contact ? found_contact : contact, + inv_session, NULL); + if (!session) { pjsip_inv_terminate(inv_session, 500, PJ_FALSE); return NULL; } @@ -1780,29 +1794,38 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response) response = 603; } - if ((session->inv_session->state == PJSIP_INV_STATE_CONFIRMED) && session->inv_session->invite_tsx) { - ast_debug(3, "Delay sending BYE to %s because of outstanding transaction...\n", - ast_sorcery_object_get_id(session->endpoint)); - /* If this is delayed the only thing that will happen is a BYE request so we don't - * actually need to store the response code for when it happens. - */ - delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE); - } else if (session->inv_session->state == PJSIP_INV_STATE_NULL) { + switch (session->inv_session->state) { + case PJSIP_INV_STATE_NULL: pjsip_inv_terminate(session->inv_session, response, PJ_TRUE); - } else if (((status = pjsip_inv_end_session(session->inv_session, response, NULL, &packet)) == PJ_SUCCESS) - && packet) { - struct ast_sip_session_delayed_request *delay; - - /* Flush any delayed requests so they cannot overlap this transaction. */ - while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) { - ast_free(delay); + break; + case PJSIP_INV_STATE_CONFIRMED: + if (session->inv_session->invite_tsx) { + ast_debug(3, "Delay sending BYE to %s because of outstanding transaction...\n", + ast_sorcery_object_get_id(session->endpoint)); + /* If this is delayed the only thing that will happen is a BYE request so we don't + * actually need to store the response code for when it happens. + */ + delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE); + break; } + /* Fall through */ + default: + status = pjsip_inv_end_session(session->inv_session, response, NULL, &packet); + if (status == PJ_SUCCESS && packet) { + struct ast_sip_session_delayed_request *delay; - if (packet->msg->type == PJSIP_RESPONSE_MSG) { - ast_sip_session_send_response(session, packet); - } else { - ast_sip_session_send_request(session, packet); + /* Flush any delayed requests so they cannot overlap this transaction. */ + while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) { + ast_free(delay); + } + + if (packet->msg->type == PJSIP_RESPONSE_MSG) { + ast_sip_session_send_response(session, packet); + } else { + ast_sip_session_send_request(session, packet); + } } + break; } } @@ -2142,7 +2165,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata) return; } - session = ast_sip_session_alloc(endpoint, NULL, inv_session); + session = ast_sip_session_alloc(endpoint, NULL, inv_session, rdata); if (!session) { if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { pjsip_inv_terminate(inv_session, 500, PJ_FALSE); @@ -2292,7 +2315,8 @@ static void reschedule_reinvite(struct ast_sip_session *session, ast_sip_session static void __print_debug_details(const char *function, pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) { - struct ast_sip_session *session; + int id = session_module.id; + struct ast_sip_session *session = NULL; if (!DEBUG_ATLEAST(5)) { /* Debug not spamy enough */ @@ -2307,7 +2331,9 @@ static void __print_debug_details(const char *function, pjsip_inv_session *inv, pjsip_tsx_state_str(tsx->state)); return; } - session = inv->mod_data[session_module.id]; + if (id > -1) { + session = inv->mod_data[session_module.id]; + } if (!session) { ast_log(LOG_DEBUG, "inv_session %p has no ast session\n", inv); } else { @@ -2529,9 +2555,22 @@ static void session_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e) static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) { ast_sip_session_response_cb cb; - struct ast_sip_session *session = inv->mod_data[session_module.id]; + int id = session_module.id; + struct ast_sip_session *session; pjsip_tx_data *tdata; + /* + * A race condition exists at shutdown where the res_pjsip_session can be + * unloaded but this callback may still get called afterwards. In this case + * the id may end up being -1 which is useless to us. To work around this + * we store the current value and check/use it. + */ + if (id < 0) { + return; + } + + session = inv->mod_data[id]; + print_debug_details(inv, tsx, e); if (!session) { /* The session has ended. Ignore the transaction change. */ @@ -2545,10 +2584,10 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans * we transfer the data into the transaction. This way, when we receive a response, we * can dig this data out again */ - tsx->mod_data[session_module.id] = e->body.tsx_state.src.tdata->mod_data[session_module.id]; + tsx->mod_data[id] = e->body.tsx_state.src.tdata->mod_data[id]; break; case PJSIP_EVENT_RX_MSG: - cb = ast_sip_mod_data_get(tsx->mod_data, session_module.id, MOD_DATA_ON_RESPONSE); + cb = ast_sip_mod_data_get(tsx->mod_data, id, MOD_DATA_ON_RESPONSE); /* As the PJSIP invite session implementation responds with a 200 OK before we have a * chance to be invoked session supplements for BYE requests actually end up executing * in the invite session state callback as well. To prevent session supplements from @@ -2627,7 +2666,7 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans * Clear the module data now to block session_inv_on_state_changed() * from calling session_end() if it hasn't already done so. */ - inv->mod_data[session_module.id] = NULL; + inv->mod_data[id] = NULL; if (inv->state != PJSIP_INV_STATE_DISCONNECTED) { session_end(session); @@ -2650,8 +2689,8 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans * the dialog locked to get the session by other threads. */ pjsip_dlg_inc_lock(inv->dlg); - session = inv->mod_data[session_module.id]; - inv->mod_data[session_module.id] = NULL; + session = inv->mod_data[id]; + inv->mod_data[id] = NULL; pjsip_dlg_dec_lock(inv->dlg); /* diff --git a/res/res_pjsip_transport_management.c b/res/res_pjsip_transport_management.c index afd94eb1f..1cf8e5046 100644 --- a/res/res_pjsip_transport_management.c +++ b/res/res_pjsip_transport_management.c @@ -42,7 +42,7 @@ static const pj_str_t keepalive_packet = { "\r\n\r\n", 4 }; /*! \brief Global container of active transports */ -static struct ao2_container *transports; +static AO2_GLOBAL_OBJ_STATIC(monitored_transports); /*! \brief Scheduler context for timing out connections with no data received */ static struct ast_sched_context *sched; @@ -84,6 +84,7 @@ static int keepalive_transport_cb(void *obj, void *arg, int flags) /*! \brief Thread which sends keepalives to all active connection-oriented transports */ static void *keepalive_transport_thread(void *data) { + struct ao2_container *transports; pj_thread_desc desc; pj_thread_t *thread; @@ -92,6 +93,11 @@ static void *keepalive_transport_thread(void *data) return NULL; } + transports = ao2_global_obj_ref(monitored_transports); + if (!transports) { + return NULL; + } + /* Once loaded this module just keeps on going as it is unsafe to stop and change the underlying * callback for the transport manager. */ @@ -100,6 +106,7 @@ static void *keepalive_transport_thread(void *data) ao2_callback(transports, OBJ_NODATA, keepalive_transport_cb, NULL); } + ao2_ref(transports, -1); return NULL; } @@ -108,7 +115,6 @@ AST_THREADSTORAGE(desc_storage); static int idle_sched_cb(const void *data) { struct monitored_transport *keepalive = (struct monitored_transport *) data; - int sip_received = ast_atomic_fetchadd_int(&keepalive->sip_received, 0); if (!pj_thread_is_registered()) { pj_thread_t *thread; @@ -126,7 +132,7 @@ static int idle_sched_cb(const void *data) pj_thread_register("Transport Monitor", *desc, &thread); } - if (!sip_received) { + if (!keepalive->sip_received) { ast_log(LOG_NOTICE, "Shutting down transport '%s' since no request was received in %d seconds\n", keepalive->transport->info, IDLE_TIMEOUT); pjsip_transport_shutdown(keepalive->transport); @@ -148,23 +154,30 @@ static void monitored_transport_destroy(void *obj) static void monitored_transport_state_callback(pjsip_transport *transport, pjsip_transport_state state, const pjsip_transport_state_info *info) { + struct ao2_container *transports; + /* We only care about reliable transports */ - if (PJSIP_TRANSPORT_IS_RELIABLE(transport) && - (transport->dir == PJSIP_TP_DIR_INCOMING || keepalive_interval)) { + if (PJSIP_TRANSPORT_IS_RELIABLE(transport) + && (transport->dir == PJSIP_TP_DIR_INCOMING || keepalive_interval) + && (transports = ao2_global_obj_ref(monitored_transports))) { struct monitored_transport *monitored; switch (state) { case PJSIP_TP_STATE_CONNECTED: - monitored = ao2_alloc(sizeof(*monitored), monitored_transport_destroy); + monitored = ao2_alloc_options(sizeof(*monitored), + monitored_transport_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!monitored) { break; } monitored->transport = transport; pjsip_transport_add_ref(monitored->transport); + ao2_link(transports, monitored); + if (transport->dir == PJSIP_TP_DIR_INCOMING) { /* Let the scheduler inherit the reference from allocation */ if (ast_sched_add_variable(sched, IDLE_TIMEOUT, idle_sched_cb, monitored, 1) < 0) { + /* Uh Oh. Could not schedule the idle check. Kill the transport. */ ao2_unlink(transports, monitored); ao2_ref(monitored, -1); pjsip_transport_shutdown(transport); @@ -181,6 +194,8 @@ static void monitored_transport_state_callback(pjsip_transport *transport, pjsip default: break; } + + ao2_ref(transports, -1); } /* Forward to the old state callback if present */ @@ -242,7 +257,7 @@ static int monitored_transport_cmp_fn(void *obj, void *arg, int flags) break; } - return !cmp ? CMP_MATCH | CMP_STOP : 0; + return !cmp ? CMP_MATCH : 0; } static void keepalive_global_loaded(const char *object_type) @@ -265,8 +280,8 @@ static void keepalive_global_loaded(const char *object_type) if (ast_pthread_create(&keepalive_thread, NULL, keepalive_transport_thread, NULL)) { ast_log(LOG_ERROR, "Could not create thread for sending keepalive messages.\n"); - ao2_ref(transports, -1); - return; + keepalive_thread = AST_PTHREADT_NULL; + keepalive_interval = 0; } } @@ -283,14 +298,21 @@ static struct ast_sorcery_observer keepalive_global_observer = { */ static pj_bool_t idle_monitor_on_rx_request(pjsip_rx_data *rdata) { + struct ao2_container *transports; struct monitored_transport *idle_trans; + transports = ao2_global_obj_ref(monitored_transports); + if (!transports) { + return PJ_FALSE; + } + idle_trans = ao2_find(transports, rdata->tp_info.transport->obj_name, OBJ_SEARCH_KEY); + ao2_ref(transports, -1); if (!idle_trans) { return PJ_FALSE; } - ast_atomic_fetchadd_int(&idle_trans->sip_received, +1); + idle_trans->sip_received = 1; ao2_ref(idle_trans, -1); return PJ_FALSE; @@ -304,35 +326,38 @@ static pjsip_module idle_monitor_module = { static int load_module(void) { + struct ao2_container *transports; pjsip_tpmgr *tpmgr; CHECK_PJSIP_MODULE_LOADED(); + tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + if (!tpmgr) { + ast_log(LOG_ERROR, "No transport manager to attach keepalive functionality to.\n"); + return AST_MODULE_LOAD_DECLINE; + } + transports = ao2_container_alloc(TRANSPORTS_BUCKETS, monitored_transport_hash_fn, monitored_transport_cmp_fn); if (!transports) { ast_log(LOG_ERROR, "Could not create container for transports to perform keepalive on.\n"); return AST_MODULE_LOAD_DECLINE; } - - tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); - if (!tpmgr) { - ast_log(LOG_ERROR, "No transport manager to attach keepalive functionality to.\n"); - ao2_ref(transports, -1); - return AST_MODULE_LOAD_DECLINE; - } + ao2_global_obj_replace_unref(monitored_transports, transports); + ao2_ref(transports, -1); sched = ast_sched_context_create(); if (!sched) { ast_log(LOG_ERROR, "Failed to create keepalive scheduler context.\n"); - ao2_ref(transports, -1); + ao2_global_obj_release(monitored_transports); return AST_MODULE_LOAD_DECLINE; } if (ast_sched_start_thread(sched)) { ast_log(LOG_ERROR, "Failed to start keepalive scheduler thread\n"); ast_sched_context_destroy(sched); - ao2_ref(transports, -1); + sched = NULL; + ao2_global_obj_release(monitored_transports); return AST_MODULE_LOAD_DECLINE; } @@ -343,25 +368,38 @@ static int load_module(void) ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &keepalive_global_observer); ast_sorcery_reload_object(ast_sip_get_sorcery(), "global"); + ast_module_shutdown_ref(ast_module_info->self); return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { - pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + pjsip_tpmgr *tpmgr; if (keepalive_interval) { keepalive_interval = 0; - pthread_kill(keepalive_thread, SIGURG); - pthread_join(keepalive_thread, NULL); + if (keepalive_thread != AST_PTHREADT_NULL) { + pthread_kill(keepalive_thread, SIGURG); + pthread_join(keepalive_thread, NULL); + keepalive_thread = AST_PTHREADT_NULL; + } } - ast_sched_context_destroy(sched); - ao2_ref(transports, -1); + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &keepalive_global_observer); + + tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + if (tpmgr) { + pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback); + } ast_sip_unregister_service(&idle_monitor_module); - pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback); + + ast_sched_context_destroy(sched); + sched = NULL; + + ao2_global_obj_release(monitored_transports); + return 0; } @@ -372,9 +410,9 @@ static int reload_module(void) } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Reliable Transport Management", - .support_level = AST_MODULE_SUPPORT_CORE, - .load = load_module, - .reload = reload_module, - .unload = unload_module, - .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4, - ); + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .reload = reload_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4, +); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index b2ecf5962..feab1ca80 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -540,8 +540,8 @@ static int ice_candidate_cmp(void *obj, void *arg, int flags) if (strcmp(candidate1->foundation, candidate2->foundation) || candidate1->id != candidate2->id || - ast_sockaddr_cmp(&candidate1->address, &candidate2->address) || - candidate1->type != candidate1->type) { + candidate1->type != candidate2->type || + ast_sockaddr_cmp(&candidate1->address, &candidate2->address)) { return 0; } @@ -1357,7 +1357,12 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con return 0; } - if (!(rtp->ssl_ctx = SSL_CTX_new(DTLSv1_method()))) { +#if OPENSSL_VERSION_NUMBER < 0x10002000L + rtp->ssl_ctx = SSL_CTX_new(DTLSv1_method()); +#else + rtp->ssl_ctx = SSL_CTX_new(DTLS_method()); +#endif + if (!rtp->ssl_ctx) { return -1; } @@ -1393,7 +1398,7 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con if (!ast_strlen_zero(dtls_cfg->certfile)) { char *private = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile; BIO *certbio; - X509 *cert; + X509 *cert = NULL; const EVP_MD *type; unsigned int size, i; unsigned char fingerprint[EVP_MAX_MD_SIZE]; @@ -1435,6 +1440,9 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con ast_log(LOG_ERROR, "Could not produce fingerprint from certificate '%s' for RTP instance '%p'\n", dtls_cfg->certfile, instance); BIO_free_all(certbio); + if (cert) { + X509_free(cert); + } return -1; } @@ -1446,6 +1454,7 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con *(local_fingerprint-1) = 0; BIO_free_all(certbio); + X509_free(cert); } if (!ast_strlen_zero(dtls_cfg->cipher)) { diff --git a/res/res_rtp_multicast.c b/res/res_rtp_multicast.c index 8327cf20a..53bdf14a4 100644 --- a/res/res_rtp_multicast.c +++ b/res/res_rtp_multicast.c @@ -54,6 +54,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "asterisk/rtp_engine.h" #include "asterisk/format_cache.h" +#include "asterisk/multicast_rtp.h" +#include "asterisk/app.h" /*! Command value used for Linksys paging to indicate we are starting */ #define LINKSYS_MCAST_STARTCMD 6 @@ -63,8 +65,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*! \brief Type of paging to do */ enum multicast_type { + /*! Type has not been set yet */ + MULTICAST_TYPE_UNSPECIFIED = 0, /*! Simple multicast enabled client/receiver paging like Snom and Barix uses */ - MULTICAST_TYPE_BASIC = 0, + MULTICAST_TYPE_BASIC, /*! More advanced Linksys type paging which requires a start and stop packet */ MULTICAST_TYPE_LINKSYS, }; @@ -95,6 +99,91 @@ struct multicast_rtp { struct timeval txcore; }; +enum { + OPT_CODEC = (1 << 0), + OPT_LOOP = (1 << 1), + OPT_TTL = (1 << 2), + OPT_IF = (1 << 3), +}; + +enum { + OPT_ARG_CODEC = 0, + OPT_ARG_LOOP, + OPT_ARG_TTL, + OPT_ARG_IF, + OPT_ARG_ARRAY_SIZE, +}; + +AST_APP_OPTIONS(multicast_rtp_options, BEGIN_OPTIONS + /*! Set the codec to be used for multicast RTP */ + AST_APP_OPTION_ARG('c', OPT_CODEC, OPT_ARG_CODEC), + /*! Set whether multicast RTP is looped back to the sender */ + AST_APP_OPTION_ARG('l', OPT_LOOP, OPT_ARG_LOOP), + /*! Set the hop count for multicast RTP */ + AST_APP_OPTION_ARG('t', OPT_TTL, OPT_ARG_TTL), + /*! Set the interface from which multicast RTP is sent */ + AST_APP_OPTION_ARG('i', OPT_IF, OPT_ARG_IF), +END_OPTIONS ); + +struct ast_multicast_rtp_options { + char *type; + char *options; + struct ast_format *fmt; + struct ast_flags opts; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + /*! The type and options are stored in this buffer */ + char buf[0]; +}; + +struct ast_multicast_rtp_options *ast_multicast_rtp_create_options(const char *type, + const char *options) +{ + struct ast_multicast_rtp_options *mcast_options; + char *pos; + + mcast_options = ast_calloc(1, sizeof(*mcast_options) + + strlen(type) + + strlen(options) + 2); + if (!mcast_options) { + return NULL; + } + + pos = mcast_options->buf; + + /* Safe */ + strcpy(pos, type); + mcast_options->type = pos; + pos += strlen(type) + 1; + + /* Safe */ + strcpy(pos, options); + mcast_options->options = pos; + + if (ast_app_parse_options(multicast_rtp_options, &mcast_options->opts, + mcast_options->opt_args, mcast_options->options)) { + ast_log(LOG_WARNING, "Error parsing multicast RTP options\n"); + ast_multicast_rtp_free_options(mcast_options); + return NULL; + } + + return mcast_options; +} + +void ast_multicast_rtp_free_options(struct ast_multicast_rtp_options *mcast_options) +{ + ast_free(mcast_options); +} + +struct ast_format *ast_multicast_rtp_options_get_format(struct ast_multicast_rtp_options *mcast_options) +{ + if (ast_test_flag(&mcast_options->opts, OPT_CODEC) + && !ast_strlen_zero(mcast_options->opt_args[OPT_ARG_CODEC])) { + return ast_format_cache_get(mcast_options->opt_args[OPT_ARG_CODEC]); + } + + return NULL; +} + /* Forward Declarations */ static int multicast_rtp_new(struct ast_rtp_instance *instance, struct ast_sched_context *sched, struct ast_sockaddr *addr, void *data); static int multicast_rtp_activate(struct ast_rtp_instance *instance); @@ -112,21 +201,93 @@ static struct ast_rtp_engine multicast_rtp_engine = { .read = multicast_rtp_read, }; +static int set_type(struct multicast_rtp *multicast, const char *type) +{ + if (!strcasecmp(type, "basic")) { + multicast->type = MULTICAST_TYPE_BASIC; + } else if (!strcasecmp(type, "linksys")) { + multicast->type = MULTICAST_TYPE_LINKSYS; + } else { + ast_log(LOG_WARNING, "Unrecognized multicast type '%s' specified.\n", type); + return -1; + } + + return 0; +} + +static void set_ttl(int sock, const char *ttl_str) +{ + int ttl; + + if (ast_strlen_zero(ttl_str)) { + return; + } + + ast_debug(3, "Setting multicast TTL to %s\n", ttl_str); + + if (sscanf(ttl_str, "%30d", &ttl) < 1) { + ast_log(LOG_WARNING, "Invalid multicast ttl option '%s'\n", ttl_str); + return; + } + + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) { + ast_log(LOG_WARNING, "Could not set multicast ttl to '%s': %s\n", + ttl_str, strerror(errno)); + } +} + +static void set_loop(int sock, const char *loop_str) +{ + unsigned char loop; + + if (ast_strlen_zero(loop_str)) { + return; + } + + ast_debug(3, "Setting multicast loop to %s\n", loop_str); + + if (sscanf(loop_str, "%30hhu", &loop) < 1) { + ast_log(LOG_WARNING, "Invalid multicast loop option '%s'\n", loop_str); + return; + } + + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { + ast_log(LOG_WARNING, "Could not set multicast loop to '%s': %s\n", + loop_str, strerror(errno)); + } +} + +static void set_if(int sock, const char *if_str) +{ + struct in_addr iface; + + if (ast_strlen_zero(if_str)) { + return; + } + + ast_debug(3, "Setting multicast if to %s\n", if_str); + + if (!inet_aton(if_str, &iface)) { + ast_log(LOG_WARNING, "Cannot parse if option '%s'\n", if_str); + } + + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)) < 0) { + ast_log(LOG_WARNING, "Could not set multicast if to '%s': %s\n", + if_str, strerror(errno)); + } +} + /*! \brief Function called to create a new multicast instance */ static int multicast_rtp_new(struct ast_rtp_instance *instance, struct ast_sched_context *sched, struct ast_sockaddr *addr, void *data) { struct multicast_rtp *multicast; - const char *type = data; + struct ast_multicast_rtp_options *mcast_options = data; if (!(multicast = ast_calloc(1, sizeof(*multicast)))) { return -1; } - if (!strcasecmp(type, "basic")) { - multicast->type = MULTICAST_TYPE_BASIC; - } else if (!strcasecmp(type, "linksys")) { - multicast->type = MULTICAST_TYPE_LINKSYS; - } else { + if (set_type(multicast, mcast_options->type)) { ast_free(multicast); return -1; } @@ -136,6 +297,18 @@ static int multicast_rtp_new(struct ast_rtp_instance *instance, struct ast_sched return -1; } + if (ast_test_flag(&mcast_options->opts, OPT_LOOP)) { + set_loop(multicast->socket, mcast_options->opt_args[OPT_ARG_LOOP]); + } + + if (ast_test_flag(&mcast_options->opts, OPT_TTL)) { + set_ttl(multicast->socket, mcast_options->opt_args[OPT_ARG_TTL]); + } + + if (ast_test_flag(&mcast_options->opts, OPT_IF)) { + set_if(multicast->socket, mcast_options->opt_args[OPT_ARG_IF]); + } + multicast->ssrc = ast_random(); ast_rtp_instance_set_data(instance, multicast); @@ -314,7 +487,7 @@ static int unload_module(void) return 0; } -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Multicast RTP Engine", +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Multicast RTP Engine", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, diff --git a/res/res_rtp_multicast.exports.in b/res/res_rtp_multicast.exports.in new file mode 100644 index 000000000..995a1802e --- /dev/null +++ b/res/res_rtp_multicast.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXast_multicast_rtp*; + local: + *; +}; diff --git a/res/res_srtp.c b/res/res_srtp.c index 8d8daf0b0..97773c125 100644 --- a/res/res_srtp.c +++ b/res/res_srtp.c @@ -40,7 +40,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <srtp/srtp.h> +#ifdef HAVE_OPENSSL +#include <openssl/rand.h> +#else #include <srtp/crypto_kernel.h> +#endif #include "asterisk/lock.h" #include "asterisk/sched.h" @@ -305,7 +309,11 @@ static int ast_srtp_policy_set_master_key(struct ast_srtp_policy *policy, const static int ast_srtp_get_random(unsigned char *key, size_t len) { +#ifdef HAVE_OPENSSL + return RAND_bytes(key, len) > 0 ? 0: -1; +#else return crypto_get_random(key, len) != err_status_ok ? -1: 0; +#endif } static void ast_srtp_set_cb(struct ast_srtp *srtp, const struct ast_srtp_cb *cb, void *data) diff --git a/res/res_stasis.c b/res/res_stasis.c index fae9aa220..11aeb438e 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -713,6 +713,22 @@ int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge, return 0; } +void stasis_app_bridge_playback_channel_remove(char *bridge_id, + struct stasis_app_control *control) +{ + struct stasis_app_bridge_channel_wrapper *wrapper; + + wrapper = ao2_find(app_bridges_playback, bridge_id, OBJ_SEARCH_KEY | OBJ_UNLINK); + if (wrapper) { + /* If wrapper is not found, then that means the after bridge callback has been + * called or is in progress. No need to unlink the control here since that has + * been done or is about to be done in the after bridge callback + */ + ao2_unlink(app_controls, control); + ao2_ref(wrapper, -1); + } +} + struct ast_channel *stasis_app_bridge_playback_channel_find(struct ast_bridge *bridge) { struct stasis_app_bridge_channel_wrapper *playback_wrapper; diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index cb41fb681..8eaa5eb9b 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -128,6 +128,14 @@ "required": false, "allowMultiple": false, "dataType": "string" + }, + { + "name": "formats", + "description": "The format name capability list to use if originator is not specified. Ex. \"ulaw,slin16\". Format names can be found with \"core show codecs\".", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" } ], "errorResponses": [ @@ -276,6 +284,14 @@ "required": false, "allowMultiple": false, "dataType": "string" + }, + { + "name": "formats", + "description": "The format name capability list to use if originator is not specified. Ex. \"ulaw,slin16\". Format names can be found with \"core show codecs\".", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" } ], "errorResponses": [ @@ -284,7 +300,6 @@ "reason": "Invalid parameters for originating a channel." } ] - }, { "httpMethod": "DELETE", diff --git a/tests/test_cel.c b/tests/test_cel.c index 03e243c78..9a3dc8114 100644 --- a/tests/test_cel.c +++ b/tests/test_cel.c @@ -1610,7 +1610,7 @@ AST_TEST_DEFINE(test_cel_dial_pickup) ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); - HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "CANCEL"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "ANSWER"); HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; diff --git a/tests/test_netsock2.c b/tests/test_netsock2.c index e182b0a75..fec1ae2d5 100644 --- a/tests/test_netsock2.c +++ b/tests/test_netsock2.c @@ -75,7 +75,7 @@ AST_TEST_DEFINE(parsing) }; size_t x; - struct ast_sockaddr addr = { { 0, 0, } }; + struct ast_sockaddr addr; int parse_result; switch (cmd) { @@ -91,15 +91,17 @@ AST_TEST_DEFINE(parsing) } for (x = 0; x < ARRAY_LEN(test_vals); x++) { + memset(&addr, 0, sizeof(addr)); if ((parse_result = ast_sockaddr_parse(&addr, test_vals[x].address, 0)) != test_vals[x].expected_result) { ast_test_status_update(test, "On '%s' expected %d but got %d\n", test_vals[x].address, test_vals[x].expected_result, parse_result); res = AST_TEST_FAIL; } if (parse_result) { - struct ast_sockaddr tmp_addr = { { 0, 0, } }; + struct ast_sockaddr tmp_addr; const char *tmp; tmp = ast_sockaddr_stringify(&addr); + memset(&tmp_addr, 0, sizeof(tmp_addr)); ast_sockaddr_parse(&tmp_addr, tmp, 0); if (ast_sockaddr_cmp_addr(&addr, &tmp_addr)) { char buf[64]; diff --git a/tests/test_res_pjsip_scheduler.c b/tests/test_res_pjsip_scheduler.c index f9a1633ac..a5461accb 100644 --- a/tests/test_res_pjsip_scheduler.c +++ b/tests/test_res_pjsip_scheduler.c @@ -26,6 +26,7 @@ /*** MODULEINFO <depend>TEST_FRAMEWORK</depend> + <depend>pjproject</depend> <depend>res_pjsip</depend> <support_level>core</support_level> ***/ diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4 index 2cc18bfa8..67ac04d4d 100644 --- a/third-party/pjproject/configure.m4 +++ b/third-party/pjproject/configure.m4 @@ -44,4 +44,5 @@ AC_DEFUN([PJPROJECT_CONFIGURE], PJPROJECT_SYMBOL_CHECK([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_from_files2], [pjlib.h]) PJPROJECT_SYMBOL_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip_endpt_set_ext_resolver], [pjsip.h]) AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_PROTO], 1, [Define if your system has PJSIP_TLS_TRANSPORT_PROTO]) + AC_DEFINE([HAVE_PJSIP_EVSUB_GRP_LOCK], 1, [Define if your system has PJSIP_EVSUB_GRP_LOCK]) ]) diff --git a/third-party/pjproject/patches/0001-evsub-Add-APIs-to-add-decrement-an-event-subscriptio.patch b/third-party/pjproject/patches/0001-evsub-Add-APIs-to-add-decrement-an-event-subscriptio.patch new file mode 100644 index 000000000..d2a47c6c5 --- /dev/null +++ b/third-party/pjproject/patches/0001-evsub-Add-APIs-to-add-decrement-an-event-subscriptio.patch @@ -0,0 +1,73 @@ +From a5030c9b33b2c936879fbacb1d2ea5edc2979181 Mon Sep 17 00:00:00 2001 +From: George Joseph <gjoseph@digium.com> +Date: Sat, 18 Jun 2016 10:14:34 -0600 +Subject: [PATCH] evsub: Add APIs to add/decrement an event subscription's + group lock + +These APIs can be used to ensure that the evsub isn't destroyed before +an application is finished using it. +--- + pjsip/include/pjsip-simple/evsub.h | 20 ++++++++++++++++++++ + pjsip/src/pjsip-simple/evsub.c | 14 ++++++++++++++ + 2 files changed, 34 insertions(+) + +diff --git a/pjsip/include/pjsip-simple/evsub.h b/pjsip/include/pjsip-simple/evsub.h +index 2dc4d69..31f85f8 100644 +--- a/pjsip/include/pjsip-simple/evsub.h ++++ b/pjsip/include/pjsip-simple/evsub.h +@@ -490,6 +490,26 @@ PJ_DECL(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id, + PJ_DECL(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id ); + + ++/** ++ * Increment the event subscription's group lock. ++ * ++ * @param sub The server subscription instance. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub); ++ ++ ++/** ++ * Decrement the event subscription's group lock. ++ * ++ * @param sub The server subscription instance. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub); ++ ++ + + PJ_END_DECL + +diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c +index 7cd8859..68a9564 100644 +--- a/pjsip/src/pjsip-simple/evsub.c ++++ b/pjsip/src/pjsip-simple/evsub.c +@@ -831,7 +831,21 @@ static pj_status_t evsub_create( pjsip_dialog *dlg, + return PJ_SUCCESS; + } + ++/* ++ * Increment the event subscription's group lock. ++ */ ++PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub) ++{ ++ return pj_grp_lock_add_ref(sub->grp_lock); ++} + ++/* ++ * Decrement the event subscription's group lock. ++ */ ++PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub) ++{ ++ return pj_grp_lock_dec_ref(sub->grp_lock); ++} + + /* + * Create client subscription session. +-- +2.5.5 + diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h index 8e854b723..eb9f8b15c 100644 --- a/third-party/pjproject/patches/config_site.h +++ b/third-party/pjproject/patches/config_site.h @@ -37,3 +37,7 @@ #undef PJ_TODO #define PJ_TODO(x) + +/* Defaults too low for WebRTC */ +#define PJ_ICE_MAX_CAND 32 +#define PJ_ICE_MAX_CHECKS (PJ_ICE_MAX_CAND * 2) |