diff options
Diffstat (limited to 'res')
52 files changed, 1975 insertions, 499 deletions
diff --git a/res/res_ari_applications.c b/res/res_ari_applications.c index cb12e84c8..290719d36 100644 --- a/res/res_ari_applications.c +++ b/res/res_ari_applications.c @@ -502,6 +502,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&applications); if (res) { diff --git a/res/res_ari_asterisk.c b/res/res_ari_asterisk.c index 1a574aaaf..73e4d0ce3 100644 --- a/res/res_ari_asterisk.c +++ b/res/res_ari_asterisk.c @@ -1223,6 +1223,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&asterisk); if (res) { diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c index 69d4d6ed5..b92333095 100644 --- a/res/res_ari_bridges.c +++ b/res/res_ari_bridges.c @@ -1415,6 +1415,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&bridges); if (res) { diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index f59f20634..621767980 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -2479,6 +2479,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&channels); if (res) { diff --git a/res/res_ari_device_states.c b/res/res_ari_device_states.c index a3711e6eb..fe1817d5d 100644 --- a/res/res_ari_device_states.c +++ b/res/res_ari_device_states.c @@ -333,6 +333,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&deviceStates); if (res) { diff --git a/res/res_ari_endpoints.c b/res/res_ari_endpoints.c index 43d255898..a46b0dc61 100644 --- a/res/res_ari_endpoints.c +++ b/res/res_ari_endpoints.c @@ -457,6 +457,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&endpoints); if (res) { diff --git a/res/res_ari_events.c b/res/res_ari_events.c index fd208c57b..b6a44d9b9 100644 --- a/res/res_ari_events.c +++ b/res/res_ari_events.c @@ -430,22 +430,29 @@ static int unload_module(void) static int load_module(void) { int res = 0; - struct ast_websocket_protocol *protocol; - events.ws_server = ast_websocket_server_create(); - if (!events.ws_server) { - return AST_MODULE_LOAD_DECLINE; - } + CHECK_ARI_MODULE_LOADED(); - protocol = ast_websocket_sub_protocol_alloc("ari"); - if (!protocol) { - ao2_ref(events.ws_server, -1); - events.ws_server = NULL; - return AST_MODULE_LOAD_DECLINE; + /* This is scoped to not conflict with CHECK_ARI_MODULE_LOADED */ + { + struct ast_websocket_protocol *protocol; + + events.ws_server = ast_websocket_server_create(); + if (!events.ws_server) { + return AST_MODULE_LOAD_DECLINE; + } + + protocol = ast_websocket_sub_protocol_alloc("ari"); + if (!protocol) { + ao2_ref(events.ws_server, -1); + events.ws_server = NULL; + return AST_MODULE_LOAD_DECLINE; + } + protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb; + protocol->session_established = ast_ari_events_event_websocket_ws_established_cb; + res |= ast_websocket_server_add_protocol2(events.ws_server, protocol); } - protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb; - protocol->session_established = ast_ari_events_event_websocket_ws_established_cb; - res |= ast_websocket_server_add_protocol2(events.ws_server, protocol); + stasis_app_ref(); res |= ast_ari_add_handler(&events); if (res) { diff --git a/res/res_ari_mailboxes.c b/res/res_ari_mailboxes.c index f85541cf0..600ecfd48 100644 --- a/res/res_ari_mailboxes.c +++ b/res/res_ari_mailboxes.c @@ -339,6 +339,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&mailboxes); if (res) { diff --git a/res/res_ari_playbacks.c b/res/res_ari_playbacks.c index 25e211c55..106463b5b 100644 --- a/res/res_ari_playbacks.c +++ b/res/res_ari_playbacks.c @@ -291,6 +291,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&playbacks); if (res) { diff --git a/res/res_ari_recordings.c b/res/res_ari_recordings.c index 29720a84a..c43148d83 100644 --- a/res/res_ari_recordings.c +++ b/res/res_ari_recordings.c @@ -807,6 +807,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&recordings); if (res) { diff --git a/res/res_ari_sounds.c b/res/res_ari_sounds.c index 6d09d2cbe..e58ecd1cf 100644 --- a/res/res_ari_sounds.c +++ b/res/res_ari_sounds.c @@ -221,6 +221,10 @@ static int unload_module(void) static int load_module(void) { int res = 0; + + CHECK_ARI_MODULE_LOADED(); + + stasis_app_ref(); res |= ast_ari_add_handler(&sounds); if (res) { diff --git a/res/res_calendar.c b/res/res_calendar.c index 3725c9435..298970a92 100644 --- a/res/res_calendar.c +++ b/res/res_calendar.c @@ -735,7 +735,7 @@ static void *do_notify(void *data) struct ast_channel *chan = NULL; struct ast_variable *itervar; char *tech, *dest; - char buf[8]; + char buf[33]; struct ast_format_cap *caps; tech = ast_strdupa(event->owner->notify_channel); diff --git a/res/res_calendar_icalendar.c b/res/res_calendar_icalendar.c index 8ac905174..a6ce62708 100644 --- a/res/res_calendar_icalendar.c +++ b/res/res_calendar_icalendar.c @@ -335,7 +335,7 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span, start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()); end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()); end_time.second += pvt->owner->timeframe * 60; - icaltime_normalize(end_time); + end_time = icaltime_normalize(end_time); for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT); iter; diff --git a/res/res_config_pgsql.c b/res/res_config_pgsql.c index e74b73036..b0a24c464 100644 --- a/res/res_config_pgsql.c +++ b/res/res_config_pgsql.c @@ -1329,7 +1329,7 @@ static int require_pgsql(const char *database, const char *tablename, va_list ap /* Size is minimum length; make it at least 50% greater, * just to be sure, because PostgreSQL doesn't support * resizing columns. */ - snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)", + snprintf(fieldtype, sizeof(fieldtype), "CHAR(%hhu)", size < 15 ? size * 2 : (size * 3 / 2 > 255) ? 255 : size * 3 / 2); } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) { diff --git a/res/res_corosync.c b/res/res_corosync.c index 6bbbc34b9..ce94e4151 100644 --- a/res/res_corosync.c +++ b/res/res_corosync.c @@ -79,6 +79,15 @@ struct corosync_node { struct ast_sockaddr addr; }; +/*! \brief Corosync ipc dispatch/request and reply size */ +#define COROSYNC_IPC_BUFFER_SIZE (8192 * 128) + +/*! \brief Version of pthread_create to ensure stack is large enough */ +#define corosync_pthread_create_background(a, b, c, d) \ + ast_pthread_create_stack(a, b, c, d, \ + (AST_BACKGROUND_STACKSIZE + (3 * COROSYNC_IPC_BUFFER_SIZE)), \ + __FILE__, __FUNCTION__, __LINE__, #c) + static struct corosync_node *corosync_node_alloc(struct ast_event *event) { struct corosync_node *node; @@ -810,10 +819,21 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_ for (i = 1, cs_err = cpg_iteration_next(cpg_iter, &cpg_desc); cs_err == CS_OK; cs_err = cpg_iteration_next(cpg_iter, &cpg_desc), i++) { +#ifdef HAVE_COROSYNC_CFG_STATE_TRACK corosync_cfg_node_address_t addrs[8]; int num_addrs = 0; unsigned int j; +#endif + + ast_cli(a->fd, "=== Node %u\n", i); + ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value); +#ifdef HAVE_COROSYNC_CFG_STATE_TRACK + /* + * Corosync 2.x cfg lib needs to allocate 1M on stack after calling + * corosync_cfg_get_node_addrs. netconsole thread has allocated only 0.5M + * resulting in crash. + */ cs_err = corosync_cfg_get_node_addrs(cfg_handle, cpg_desc.nodeid, ARRAY_LEN(addrs), &num_addrs, addrs); if (cs_err != CS_OK) { @@ -821,9 +841,6 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_ continue; } - ast_cli(a->fd, "=== Node %u\n", i); - ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value); - for (j = 0; j < num_addrs; j++) { struct sockaddr *sa = (struct sockaddr *) addrs[j].address; size_t sa_len = (size_t) addrs[j].address_length; @@ -833,7 +850,9 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_ ast_cli(a->fd, "=== --> Address %u: %s\n", j + 1, buf); } - +#else + ast_cli(a->fd, "=== --> Nodeid: %"PRIu32"\n", cpg_desc.nodeid); +#endif } ast_cli(a->fd, "===\n" @@ -1159,7 +1178,7 @@ static int load_module(void) goto failed; } - if (ast_pthread_create_background(&dispatch_thread.id, NULL, + if (corosync_pthread_create_background(&dispatch_thread.id, NULL, dispatch_thread_handler, NULL)) { ast_log(LOG_ERROR, "Error starting CPG dispatch thread.\n"); goto failed; diff --git a/res/res_http_post.c b/res/res_http_post.c index 2ee792af9..3e1ed03f6 100644 --- a/res/res_http_post.c +++ b/res/res_http_post.c @@ -57,6 +57,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #ifdef GMIME_TYPE_CONTENT_TYPE #define AST_GMIME_VER_24 #endif +#if GMIME_MAJOR_VERSION >= 3 +#define AST_GMIME_VER_30 +#endif /* just a little structure to hold callback info for gmime */ struct mime_cbinfo { @@ -86,7 +89,11 @@ static void post_raw(GMimePart *part, const char *post_dir, const char *fn) stream = g_mime_stream_fs_new(fd); +#ifdef AST_GMIME_VER_30 + content = g_mime_part_get_content(part); +#else content = g_mime_part_get_content_object(part); +#endif g_mime_data_wrapper_write_to_stream(content, stream); g_mime_stream_flush(stream); @@ -109,7 +116,11 @@ static GMimeMessage *parse_message(FILE *f) g_object_unref(stream); - message = g_mime_parser_construct_message(parser); + message = g_mime_parser_construct_message(parser +#ifdef AST_GMIME_VER_30 + , NULL +#endif + ); g_object_unref(parser); @@ -488,7 +499,11 @@ static int reload(void) static int load_module(void) { - g_mime_init(0); + g_mime_init( +#ifndef AST_GMIME_VER_30 + 0 +#endif + ); __ast_http_post_load(0); diff --git a/res/res_monitor.c b/res/res_monitor.c index ebf98439c..c4ee674f9 100644 --- a/res/res_monitor.c +++ b/res/res_monitor.c @@ -62,17 +62,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <syntax> <parameter name="file_format" argsep=":"> <argument name="file_format" required="true"> - <para>optional, if not set, defaults to <literal>wav</literal></para> + <para>Optional. If not set, defaults to <literal>wav</literal></para> </argument> <argument name="urlbase" /> </parameter> <parameter name="fname_base"> - <para>if set, changes the filename used to the one specified.</para> + <para>If set, changes the filename used to the one specified.</para> </parameter> <parameter name="options"> <optionlist> <option name="m"> - <para>when the recording ends mix the two leg files into one and + <para>When the recording ends mix the two leg files into one and delete the two leg files. If the variable <variable>MONITOR_EXEC</variable> is set, the application referenced in it will be executed instead of soxmix/sox and the raw leg files will NOT be deleted automatically. @@ -83,6 +83,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") will be passed on as additional arguments to <variable>MONITOR_EXEC</variable>. Both <variable>MONITOR_EXEC</variable> and the Mix flag can be set from the administrator interface.</para> + <warning><para>Do not use untrusted strings such as + <variable>CALLERID(num)</variable> or <variable>CALLERID(name)</variable> + as part of <variable>MONITOR_EXEC</variable> or + <variable>MONITOR_EXEC_ARGS</variable>. You risk a command injection + attack executing arbitrary commands if the untrusted strings aren't + filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </option> <option name="b"> <para>Don't begin recording unless a call is bridged to another channel.</para> @@ -460,7 +467,7 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l LOCK_IF_NEEDED(chan, need_lock); if (ast_channel_monitor(chan)) { - char filename[ FILENAME_MAX ]; + RAII_VAR(struct ast_str *, tmp, ast_str_create(1024), ast_free); if (ast_channel_monitor(chan)->read_stream) { ast_closestream(ast_channel_monitor(chan)->read_stream); @@ -469,31 +476,29 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l ast_closestream(ast_channel_monitor(chan)->write_stream); } - if (ast_channel_monitor(chan)->filename_changed && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) { + if (tmp && ast_channel_monitor(chan)->filename_changed && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) { if (ast_fileexists(ast_channel_monitor(chan)->read_filename,NULL,NULL) > 0) { - snprintf(filename, FILENAME_MAX, "%s-in", ast_channel_monitor(chan)->filename_base); - if (ast_fileexists(filename, NULL, NULL) > 0) { - ast_filedelete(filename, NULL); + ast_str_set(&tmp, 0, "%s-in", ast_channel_monitor(chan)->filename_base); + if (ast_fileexists(ast_str_buffer(tmp), NULL, NULL) > 0) { + ast_filedelete(ast_str_buffer(tmp), NULL); } - ast_filerename(ast_channel_monitor(chan)->read_filename, filename, ast_channel_monitor(chan)->format); + ast_filerename(ast_channel_monitor(chan)->read_filename, ast_str_buffer(tmp), ast_channel_monitor(chan)->format); } else { ast_log(LOG_WARNING, "File %s not found\n", ast_channel_monitor(chan)->read_filename); } - if (ast_fileexists(ast_channel_monitor(chan)->write_filename,NULL,NULL) > 0) { - snprintf(filename, FILENAME_MAX, "%s-out", ast_channel_monitor(chan)->filename_base); - if (ast_fileexists(filename, NULL, NULL) > 0) { - ast_filedelete(filename, NULL); + if (tmp && ast_fileexists(ast_channel_monitor(chan)->write_filename,NULL,NULL) > 0) { + ast_str_set(&tmp, 0, "%s-out", ast_channel_monitor(chan)->filename_base); + if (ast_fileexists(ast_str_buffer(tmp), NULL, NULL) > 0) { + ast_filedelete(ast_str_buffer(tmp), NULL); } - ast_filerename(ast_channel_monitor(chan)->write_filename, filename, ast_channel_monitor(chan)->format); + ast_filerename(ast_channel_monitor(chan)->write_filename, ast_str_buffer(tmp), ast_channel_monitor(chan)->format); } else { ast_log(LOG_WARNING, "File %s not found\n", ast_channel_monitor(chan)->write_filename); } } - if (ast_channel_monitor(chan)->joinfiles && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) { - char tmp[1024]; - char tmp2[1024]; + if (tmp && ast_channel_monitor(chan)->joinfiles && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) { const char *format = !strcasecmp(ast_channel_monitor(chan)->format,"wav49") ? "WAV" : ast_channel_monitor(chan)->format; char *fname_base = ast_channel_monitor(chan)->filename_base; const char *execute, *execute_args; @@ -514,16 +519,17 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l if (ast_strlen_zero(execute_args)) { execute_args = ""; } - - snprintf(tmp, sizeof(tmp), "%s \"%s-in.%s\" \"%s-out.%s\" \"%s.%s\" %s &", + + ast_str_set(&tmp, 0, delfiles ? "( " : ""); + ast_str_append(&tmp, 0, "%s \"%s-in.%s\" \"%s-out.%s\" \"%s.%s\" %s &", execute, fname_base, format, fname_base, format, fname_base, format,execute_args); if (delfiles) { - snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s-\"* ) &",tmp, fname_base); /* remove legs when done mixing */ - ast_copy_string(tmp, tmp2, sizeof(tmp)); + /* remove legs when done mixing */ + ast_str_append(&tmp, 0, "& rm -f \"%s-\"* ) &", fname_base); } - ast_debug(1,"monitor executing %s\n",tmp); - if (ast_safe_system(tmp) == -1) - ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp); + ast_debug(1,"monitor executing %s\n", ast_str_buffer(tmp)); + if (ast_safe_system(ast_str_buffer(tmp)) == -1) + ast_log(LOG_WARNING, "Execute of %s failed.\n", ast_str_buffer(tmp)); } if (!ast_strlen_zero(ast_channel_monitor(chan)->beep_id)) { diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index c52c96428..d79151676 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -158,6 +158,11 @@ struct moh_files_state { static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */ +enum kill_methods { + KILL_METHOD_PROCESS_GROUP = 0, + KILL_METHOD_PROCESS +}; + struct mohclass { char name[MAX_MUSICCLASS]; char dir[256]; @@ -178,6 +183,10 @@ struct mohclass { int pid; time_t start; pthread_t thread; + /*! Millisecond delay between kill attempts */ + size_t kill_delay; + /*! Kill method */ + enum kill_methods kill_method; /*! Source of audio */ int srcfd; /*! Generic timer */ @@ -678,6 +687,51 @@ static int spawn_mp3(struct mohclass *class) return fds[0]; } +static int killer(pid_t pid, int signum, enum kill_methods kill_method) +{ + switch (kill_method) { + case KILL_METHOD_PROCESS_GROUP: + return killpg(pid, signum); + case KILL_METHOD_PROCESS: + return kill(pid, signum); + } + + return -1; +} + +static void killpid(int pid, size_t delay, enum kill_methods kill_method) +{ + if (killer(pid, SIGHUP, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent HUP to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } + usleep(delay); + if (killer(pid, SIGTERM, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to terminate MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent TERM to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } + usleep(delay); + if (killer(pid, SIGKILL, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to kill MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent KILL to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } +} + static void *monmp3thread(void *data) { #define MOH_MS_INTERVAL 100 @@ -753,28 +807,7 @@ static void *monmp3thread(void *data) class->srcfd = -1; pthread_testcancel(); if (class->pid > 1) { - do { - if (killpg(class->pid, SIGHUP) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(class->pid, SIGTERM) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(class->pid, SIGKILL) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno)); - } - } while (0); + killpid(class->pid, class->kill_delay, class->kill_method); class->pid = 0; } } else { @@ -1328,6 +1361,7 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char )) { class->format = ao2_bump(ast_format_slin); class->srcfd = -1; + class->kill_delay = 100000; } return class; @@ -1600,44 +1634,22 @@ static void moh_class_destructor(void *obj) if (class->pid > 1) { char buff[8192]; - int bytes, tbytes = 0, stime = 0, pid = 0; + int bytes, tbytes = 0, stime = 0; ast_debug(1, "killing %d!\n", class->pid); stime = time(NULL) + 2; - pid = class->pid; - class->pid = 0; - - /* Back when this was just mpg123, SIGKILL was fine. Now we need - * to give the process a reason and time enough to kill off its - * children. */ - do { - if (killpg(pid, SIGHUP) < 0) { - ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(pid, SIGTERM) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(pid, SIGKILL) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno)); - } - } while (0); + killpid(class->pid, class->kill_delay, class->kill_method); while ((ast_wait_for_input(class->srcfd, 100) > 0) && (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) { tbytes = tbytes + bytes; } - ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes); + ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", + class->pid, tbytes); + class->pid = 0; close(class->srcfd); class->srcfd = -1; } @@ -1765,6 +1777,22 @@ static int load_moh_classes(int reload) ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value); class->format = ao2_bump(ast_format_slin); } + } else if (!strcasecmp(var->name, "kill_escalation_delay")) { + if (sscanf(var->value, "%zu", &class->kill_delay) == 1) { + class->kill_delay *= 1000; + } else { + ast_log(LOG_WARNING, "kill_escalation_delay '%s' is invalid. Setting to 100ms\n", var->value); + class->kill_delay = 100000; + } + } else if (!strcasecmp(var->name, "kill_method")) { + if (!strcasecmp(var->value, "process")) { + class->kill_method = KILL_METHOD_PROCESS; + } else if (!strcasecmp(var->value, "process_group")){ + class->kill_method = KILL_METHOD_PROCESS_GROUP; + } else { + ast_log(LOG_WARNING, "kill_method '%s' is invalid. Setting to 'process_group'\n", var->value); + class->kill_method = KILL_METHOD_PROCESS_GROUP; + } } } @@ -1899,6 +1927,9 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>")); if (ast_test_flag(class, MOH_CUSTOM)) { ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>")); + ast_cli(a->fd, "\tKill Escalation Delay: %zu ms\n", class->kill_delay / 1000); + ast_cli(a->fd, "\tKill Method: %s\n", + class->kill_method == KILL_METHOD_PROCESS ? "process" : "process_group"); } if (strcasecmp(class->mode, "files")) { ast_cli(a->fd, "\tFormat: %s\n", ast_format_get_name(class->format)); diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 6f1c19e08..2db0668c3 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -193,11 +193,18 @@ <description> <para>Method used when updating connected line information.</para> <enumlist> - <enum name="invite" /> + <enum name="invite"> + <para>When set to <literal>invite</literal>, check the remote's Allow header and + if UPDATE is allowed, send UPDATE instead of INVITE to avoid SDP + renegotiation. If UPDATE is not Allowed, send INVITE.</para> + </enum> <enum name="reinvite"> <para>Alias for the <literal>invite</literal> value.</para> </enum> - <enum name="update" /> + <enum name="update"> + <para>If set to <literal>update</literal>, send UPDATE regardless of what the remote + Allows. </para> + </enum> </enumlist> </description> </configOption> @@ -229,6 +236,9 @@ <enum name="auto"> <para>DTMF is sent as RFC 4733 if the other side supports it or as INBAND if not.</para> </enum> + <enum name="auto_info"> + <para>DTMF is sent as RFC 4733 if the other side supports it or as SIP INFO if not.</para> + </enum> </enumlist> </description> </configOption> @@ -357,9 +367,12 @@ <configOption name="rewrite_contact"> <synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis> <description><para> - On inbound SIP messages from this endpoint, the Contact header or an appropriate Record-Route - header will be changed to have the source IP address and port. This option does not affect - outbound messages sent to this endpoint. + On inbound SIP messages from this endpoint, the Contact header or an + appropriate Record-Route header will be changed to have the source IP + address and port. This option does not affect outbound messages sent to + this endpoint. This option helps servers communicate with endpoints + that are behind NATs. This option also helps reuse reliable transport + connections such as TCP and TLS. </para></description> </configOption> <configOption name="rtp_ipv6" default="no"> @@ -965,6 +978,13 @@ will not send the progress details, but immediately will send "200 OK". </para></description> </configOption> + <configOption name="notify_early_inuse_ringing" default="no"> + <synopsis>Whether to notifies dialog-info 'early' on InUse&Ringing state</synopsis> + <description><para> + Control whether dialog-info subscriptions get 'early' state + on Ringing when already INUSE. + </para></description> + </configOption> </configObject> <configObject name="auth"> <synopsis>Authentication type</synopsis> @@ -1307,6 +1327,13 @@ in incoming SIP REGISTER requests and is not intended to be configured manually. </para></description> </configOption> + <configOption name="prune_on_boot"> + <synopsis>A contact that cannot survive a restart/boot.</synopsis> + <description><para> + The option is set if the incoming SIP REGISTER contact is rewritten + on a reliable transport and is not intended to be configured manually. + </para></description> + </configOption> </configObject> <configObject name="aor"> <synopsis>The configuration for a location of an endpoint</synopsis> @@ -3058,6 +3085,14 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint, /* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */ pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri); dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0); + if (!dlg->local.info->uri) { + ast_log(LOG_ERROR, + "Could not parse URI '%s' for endpoint '%s'\n", + dlg->local.info_str.ptr, ast_sorcery_object_get_id(endpoint)); + dlg->sess_count--; + pjsip_dlg_terminate(dlg); + return NULL; + } dlg->local.contact = pjsip_parse_hdr(dlg->pool, &HCONTACT, local_uri.ptr, local_uri.slen, NULL); @@ -4372,6 +4407,56 @@ const char *ast_sip_get_host_ip_string(int af) return NULL; } +int ast_sip_dtmf_to_str(const enum ast_sip_dtmf_mode dtmf, + char *buf, size_t buf_len) +{ + switch (dtmf) { + case AST_SIP_DTMF_NONE: + ast_copy_string(buf, "none", buf_len); + break; + case AST_SIP_DTMF_RFC_4733: + ast_copy_string(buf, "rfc4733", buf_len); + break; + case AST_SIP_DTMF_INBAND: + ast_copy_string(buf, "inband", buf_len); + break; + case AST_SIP_DTMF_INFO: + ast_copy_string(buf, "info", buf_len); + break; + case AST_SIP_DTMF_AUTO: + ast_copy_string(buf, "auto", buf_len); + break; + case AST_SIP_DTMF_AUTO_INFO: + ast_copy_string(buf, "auto_info", buf_len); + break; + default: + buf[0] = '\0'; + return -1; + } + return 0; +} + +int ast_sip_str_to_dtmf(const char * dtmf_mode) +{ + int result = -1; + + if (!strcasecmp(dtmf_mode, "info")) { + result = AST_SIP_DTMF_INFO; + } else if (!strcasecmp(dtmf_mode, "rfc4733")) { + result = AST_SIP_DTMF_RFC_4733; + } else if (!strcasecmp(dtmf_mode, "inband")) { + result = AST_SIP_DTMF_INBAND; + } else if (!strcasecmp(dtmf_mode, "none")) { + result = AST_SIP_DTMF_NONE; + } else if (!strcasecmp(dtmf_mode, "auto")) { + result = AST_SIP_DTMF_AUTO; + } else if (!strcasecmp(dtmf_mode, "auto_info")) { + result = AST_SIP_DTMF_AUTO_INFO; + } + + return result; +} + /*! * \brief Set name and number information on an identity header. * @@ -4502,6 +4587,7 @@ static int unload_pjsip(void *data) ast_sip_destroy_system(); ast_sip_destroy_global_headers(); internal_sip_unregister_service(&supplement_module); + ast_sip_destroy_transport_events(); } if (monitor_thread) { @@ -4580,7 +4666,6 @@ static int load_pjsip(void) return AST_MODULE_LOAD_SUCCESS; error: - unload_pjsip(NULL); return AST_MODULE_LOAD_DECLINE; } @@ -4646,6 +4731,11 @@ static int load_module(void) goto error; } + if (ast_sip_initialize_transport_events()) { + ast_log(LOG_ERROR, "Failed to initialize SIP transport monitor. Aborting load\n"); + goto error; + } + ast_sip_initialize_dns(); ast_sip_initialize_global_headers(); diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c index 62bc9d67d..0c804b82a 100644 --- a/res/res_pjsip/config_transport.c +++ b/res/res_pjsip/config_transport.c @@ -248,8 +248,11 @@ static int destroy_sip_transport_state(void *data) ast_free(transport_state->id); ast_free_ha(transport_state->localnet); - if (transport_state->external_address_refresher) { - ast_dnsmgr_release(transport_state->external_address_refresher); + if (transport_state->external_signaling_address_refresher) { + ast_dnsmgr_release(transport_state->external_signaling_address_refresher); + } + if (transport_state->external_media_address_refresher) { + ast_dnsmgr_release(transport_state->external_media_address_refresher); } if (transport_state->transport) { pjsip_transport_shutdown(transport_state->transport); @@ -399,8 +402,8 @@ static void copy_state_to_transport(struct ast_sip_transport *transport) memcpy(&transport->tls, &transport->state->tls, sizeof(transport->tls)); memcpy(&transport->ciphers, &transport->state->ciphers, sizeof(transport->ciphers)); transport->localnet = transport->state->localnet; - transport->external_address_refresher = transport->state->external_address_refresher; - memcpy(&transport->external_address, &transport->state->external_address, sizeof(transport->external_address)); + transport->external_address_refresher = transport->state->external_signaling_address_refresher; + memcpy(&transport->external_address, &transport->state->external_signaling_address, sizeof(transport->external_signaling_address)); } static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_transport_state *b) @@ -421,7 +424,11 @@ static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_t return -1; } - if (ast_sockaddr_cmp(&a->external_address, &b->external_address)) { + if (ast_sockaddr_cmp(&a->external_signaling_address, &b->external_signaling_address)) { + return -1; + } + + if (ast_sockaddr_cmp(&a->external_media_address, &b->external_media_address)) { return -1; } @@ -515,24 +522,41 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj) pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060); } - /* Now that we know what address family we can set up a dnsmgr refresh for the external media address if present */ + /* Now that we know what address family we can set up a dnsmgr refresh for the external addresses if present */ if (!ast_strlen_zero(transport->external_signaling_address)) { if (temp_state->state->host.addr.sa_family == pj_AF_INET()) { - temp_state->state->external_address.ss.ss_family = AF_INET; + temp_state->state->external_signaling_address.ss.ss_family = AF_INET; } else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) { - temp_state->state->external_address.ss.ss_family = AF_INET6; + temp_state->state->external_signaling_address.ss.ss_family = AF_INET6; } else { ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n", transport_id); return -1; } - if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_address, &temp_state->state->external_address_refresher, NULL) < 0) { + if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_signaling_address, &temp_state->state->external_signaling_address_refresher, NULL) < 0) { ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", transport_id); return -1; } } + if (!ast_strlen_zero(transport->external_media_address)) { + if (temp_state->state->host.addr.sa_family == pj_AF_INET()) { + temp_state->state->external_media_address.ss.ss_family = AF_INET; + } else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) { + temp_state->state->external_media_address.ss.ss_family = AF_INET6; + } else { + ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external media address\n", + transport_id); + return -1; + } + + if (ast_dnsmgr_lookup(transport->external_media_address, &temp_state->state->external_media_address, &temp_state->state->external_media_address_refresher, NULL) < 0) { + ast_log(LOG_ERROR, "Could not create dnsmgr for external media address on '%s'\n", transport_id); + return -1; + } + } + if (transport->type == AST_TRANSPORT_UDP) { for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) { @@ -1103,7 +1127,9 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v return 0; } - if (!(state->localnet = ast_append_ha("d", var->value, state->localnet, &error))) { + /* We use only the ast_apply_ha() which defaults to ALLOW + * ("permit"), so we add DENY rules. */ + if (!(state->localnet = ast_append_ha("deny", var->value, state->localnet, &error))) { return -1; } diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h index 11ad12c45..5766325b2 100644 --- a/res/res_pjsip/include/res_pjsip_private.h +++ b/res/res_pjsip/include/res_pjsip_private.h @@ -135,6 +135,29 @@ void ast_sip_destroy_distributor(void); /*! * \internal + * \brief Initialize the transport events notify module + * \since 13.18.0 + * + * The transport events notify module is responsible for monitoring + * when transports die and calling any registered callbacks when that + * happens. It also manages any PJPROJECT transport state callbacks + * registered to it so the callbacks be more dynamic allowing module + * loading/unloading. + * + * \retval -1 Failure + * \retval 0 Success + */ +int ast_sip_initialize_transport_events(void); + +/*! + * \internal + * \brief Destruct the transport events notify module. + * \since 13.18.0 + */ +void ast_sip_destroy_transport_events(void); + +/*! + * \internal * \brief Initialize global type on a sorcery instance * * \retval -1 failure diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index 05e19f53a..ddde5c47e 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -356,13 +356,12 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_na return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name); } -int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri, - struct timeval expiration_time, const char *path_info, const char *user_agent, - const char *via_addr, int via_port, const char *call_id, - struct ast_sip_endpoint *endpoint) +struct ast_sip_contact *ast_sip_location_create_contact(struct ast_sip_aor *aor, + const char *uri, struct timeval expiration_time, const char *path_info, + const char *user_agent, const char *via_addr, int via_port, const char *call_id, + int prune_on_boot, struct ast_sip_endpoint *endpoint) { struct ast_sip_contact *contact; - int res; char name[MAX_OBJECT_FIELD * 2 + 3]; char hash[33]; @@ -371,7 +370,7 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name); if (!contact) { - return -1; + return NULL; } ast_string_field_set(contact, uri, uri); @@ -405,14 +404,30 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri } contact->endpoint = ao2_bump(endpoint); - if (endpoint) { ast_string_field_set(contact, endpoint_name, ast_sorcery_object_get_id(endpoint)); } - res = ast_sorcery_create(ast_sip_get_sorcery(), contact); - ao2_ref(contact, -1); - return res; + contact->prune_on_boot = prune_on_boot; + + if (ast_sorcery_create(ast_sip_get_sorcery(), contact)) { + ao2_ref(contact, -1); + return NULL; + } + return contact; +} + +int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri, + struct timeval expiration_time, const char *path_info, const char *user_agent, + const char *via_addr, int via_port, const char *call_id, + struct ast_sip_endpoint *endpoint) +{ + struct ast_sip_contact *contact; + + contact = ast_sip_location_create_contact(aor, uri, expiration_time, path_info, + user_agent, via_addr, via_port, call_id, 0, endpoint); + ao2_cleanup(contact); + return contact ? 0 : -1; } int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri, @@ -448,6 +463,32 @@ int ast_sip_location_delete_contact(struct ast_sip_contact *contact) return ast_sorcery_delete(ast_sip_get_sorcery(), contact); } +static int prune_boot_contacts_cb(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + + if (contact->prune_on_boot + && !strcmp(contact->reg_server, ast_config_AST_SYSTEM_NAME ?: "")) { + ast_verb(3, "Removed contact '%s' from AOR '%s' due to system boot\n", + contact->uri, contact->aor); + ast_sip_location_delete_contact(contact); + } + + return 0; +} + +void ast_sip_location_prune_boot_contacts(void) +{ + struct ao2_container *contacts; + + contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact", + AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); + if (contacts) { + ao2_callback(contacts, 0, prune_boot_contacts_cb, NULL); + ao2_ref(contacts, -1); + } +} + /*! \brief Custom handler for translating from a string timeval to actual structure */ static int expiration_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj) { @@ -1228,6 +1269,7 @@ int ast_sip_initialize_sorcery_location(void) ast_sorcery_object_field_register(sorcery, "contact", "via_addr", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, via_addr)); ast_sorcery_object_field_register(sorcery, "contact", "via_port", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact, via_port)); ast_sorcery_object_field_register(sorcery, "contact", "call_id", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, call_id)); + ast_sorcery_object_field_register(sorcery, "contact", "prune_on_boot", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_contact, prune_on_boot)); ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0); ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration)); diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 77e31abe5..27dadb178 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -368,42 +368,29 @@ static int contact_acl_to_str(const void *obj, const intptr_t *args, char **buf) static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct ast_sip_endpoint *endpoint = obj; + enum ast_sip_dtmf_mode dtmf = ast_sip_str_to_dtmf(var->value); - if (!strcasecmp(var->value, "rfc4733")) { - endpoint->dtmf = AST_SIP_DTMF_RFC_4733; - } else if (!strcasecmp(var->value, "inband")) { - endpoint->dtmf = AST_SIP_DTMF_INBAND; - } else if (!strcasecmp(var->value, "info")) { - endpoint->dtmf = AST_SIP_DTMF_INFO; - } else if (!strcasecmp(var->value, "auto")) { - endpoint->dtmf = AST_SIP_DTMF_AUTO; - } else if (!strcasecmp(var->value, "none")) { - endpoint->dtmf = AST_SIP_DTMF_NONE; - } else { + if (dtmf == -1) { return -1; } + endpoint->dtmf = dtmf; return 0; } static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf) { const struct ast_sip_endpoint *endpoint = obj; + char dtmf_str[20]; + int result = -1; - switch (endpoint->dtmf) { - case AST_SIP_DTMF_RFC_4733 : - *buf = "rfc4733"; break; - case AST_SIP_DTMF_INBAND : - *buf = "inband"; break; - case AST_SIP_DTMF_INFO : - *buf = "info"; break; - case AST_SIP_DTMF_AUTO : - *buf = "auto"; break; - default: - *buf = "none"; - } + result = ast_sip_dtmf_to_str(endpoint->dtmf, dtmf_str, sizeof(dtmf_str)); - *buf = ast_strdup(*buf); + if (result == 0) { + *buf = ast_strdup(dtmf_str); + } else { + *buf = ast_strdup("none"); + } return 0; } @@ -1145,6 +1132,37 @@ static int tos_video_to_str(const void *obj, const intptr_t *args, char **buf) return 0; } +static int from_user_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + /* Valid non-alphanumeric characters for URI */ + char *valid_uri_marks = "-_.!~*`()"; + const char *val; + + for (val = var->value; *val; val++) { + if (!strchr(valid_uri_marks, *val) && !isdigit(*val) && !isalpha(*val)) { + ast_log(LOG_ERROR, "Error configuring endpoint '%s' - '%s' field " + "contains invalid character '%c'\n", + ast_sorcery_object_get_id(endpoint), var->name, *val); + return -1; + } + } + + ast_string_field_set(endpoint, fromuser, var->value); + + return 0; +} + +static int from_user_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + + *buf = ast_strdup(endpoint->fromuser); + + return 0; +} + static int set_var_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { @@ -1909,7 +1927,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sub_min_expiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry)); - ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromuser)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "from_user", "", from_user_handler, from_user_to_str, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine)); @@ -1941,6 +1959,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtcp_mux)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing)); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); @@ -2003,6 +2022,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod load_all_endpoints(); + ast_sip_location_prune_boot_contacts(); + return 0; } diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c index dadde2577..3f245eea0 100644 --- a/res/res_pjsip/pjsip_distributor.c +++ b/res/res_pjsip/pjsip_distributor.c @@ -140,62 +140,189 @@ static struct ast_taskprocessor *find_request_serializer(pjsip_rx_data *rdata) /*! Dialog-specific information the distributor uses */ struct distributor_dialog_data { + /*! dialog_associations ao2 container key */ + pjsip_dialog *dlg; /*! Serializer to distribute tasks to for this dialog */ struct ast_taskprocessor *serializer; /*! Endpoint associated with this dialog */ struct ast_sip_endpoint *endpoint; }; +#define DIALOG_ASSOCIATIONS_BUCKETS 251 + +static struct ao2_container *dialog_associations; + /*! * \internal + * \brief Compute a hash value on an arbitrary buffer. + * \since 13.17.0 * - * \note Call this with the dialog locked + * \param[in] pos The buffer to add to the hash + * \param[in] len The buffer length 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 + * hash of more than one buffer. + * + * This famous hash algorithm was written by Dan Bernstein and is + * commonly used. + * + * \sa http://www.cse.yorku.ca/~oz/hash.html */ -static struct distributor_dialog_data *distributor_dialog_data_alloc(pjsip_dialog *dlg) +static int buf_hash_add(const char *pos, size_t len, int hash) { - struct distributor_dialog_data *dist; + while (len--) { + hash = hash * 33 ^ *pos++; + } + + return hash; +} + +/*! + * \internal + * \brief Compute a hash value on an arbitrary buffer. + * \since 13.17.0 + * + * \param[in] pos The buffer to add to the hash + * \param[in] len The buffer length to add to the hash + * + * \details + * This version of the function is for when you need to compute a + * hash of more than one buffer. + * + * This famous hash algorithm was written by Dan Bernstein and is + * commonly used. + * + * \sa http://www.cse.yorku.ca/~oz/hash.html + */ +static int buf_hash(const char *pos, size_t len) +{ + return buf_hash_add(pos, len, 5381); +} - dist = PJ_POOL_ZALLOC_T(dlg->pool, struct distributor_dialog_data); - pjsip_dlg_set_mod_data(dlg, distributor_mod.id, dist); +static int dialog_associations_hash(const void *obj, int flags) +{ + const struct distributor_dialog_data *object; + union { + const pjsip_dialog *dlg; + const char buf[sizeof(pjsip_dialog *)]; + } key; - return dist; + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key.dlg = obj; + break; + case OBJ_SEARCH_OBJECT: + object = obj; + key.dlg = object->dlg; + break; + default: + /* Hash can only work on something with a full key. */ + ast_assert(0); + return 0; + } + return ast_str_hash_restrict(buf_hash(key.buf, sizeof(key.buf))); +} + +static int dialog_associations_cmp(void *obj, void *arg, int flags) +{ + const struct distributor_dialog_data *object_left = obj; + const struct distributor_dialog_data *object_right = arg; + const pjsip_dialog *right_key = arg; + int cmp = 0; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->dlg; + /* Fall through */ + case OBJ_SEARCH_KEY: + if (object_left->dlg == right_key) { + cmp = CMP_MATCH; + } + break; + case OBJ_SEARCH_PARTIAL_KEY: + /* There is no such thing for this container. */ + ast_assert(0); + break; + default: + cmp = 0; + break; + } + return cmp; } void ast_sip_dialog_set_serializer(pjsip_dialog *dlg, struct ast_taskprocessor *serializer) { struct distributor_dialog_data *dist; - SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); - dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + ao2_wrlock(dialog_associations); + dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!dist) { - dist = distributor_dialog_data_alloc(dlg); + if (serializer) { + dist = ao2_alloc(sizeof(*dist), NULL); + if (dist) { + dist->dlg = dlg; + dist->serializer = serializer; + ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK); + ao2_ref(dist, -1); + } + } + } else { + ao2_lock(dist); + dist->serializer = serializer; + if (!dist->serializer && !dist->endpoint) { + ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK); + } + ao2_unlock(dist); + ao2_ref(dist, -1); } - dist->serializer = serializer; + ao2_unlock(dialog_associations); } void ast_sip_dialog_set_endpoint(pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint) { struct distributor_dialog_data *dist; - SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); - dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + ao2_wrlock(dialog_associations); + dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!dist) { - dist = distributor_dialog_data_alloc(dlg); + if (endpoint) { + dist = ao2_alloc(sizeof(*dist), NULL); + if (dist) { + dist->dlg = dlg; + dist->endpoint = endpoint; + ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK); + ao2_ref(dist, -1); + } + } + } else { + ao2_lock(dist); + dist->endpoint = endpoint; + if (!dist->serializer && !dist->endpoint) { + ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK); + } + ao2_unlock(dist); + ao2_ref(dist, -1); } - dist->endpoint = endpoint; + ao2_unlock(dialog_associations); } struct ast_sip_endpoint *ast_sip_dialog_get_endpoint(pjsip_dialog *dlg) { struct distributor_dialog_data *dist; - SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); + struct ast_sip_endpoint *endpoint; - dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); - if (!dist || !dist->endpoint) { - return NULL; + dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY); + if (dist) { + ao2_lock(dist); + endpoint = ao2_bump(dist->endpoint); + ao2_unlock(dist); + ao2_ref(dist, -1); + } else { + endpoint = NULL; } - ao2_ref(dist->endpoint, +1); - return dist->endpoint; + return endpoint; } static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) @@ -227,7 +354,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) || rdata->msg_info.to->tag.slen != 0) { dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, local_tag, - remote_tag, PJ_TRUE); + remote_tag, PJ_FALSE); if (dlg) { return dlg; } @@ -265,11 +392,6 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) pj_mutex_unlock(tsx->mutex); #endif - if (!dlg) { - return NULL; - } - - pjsip_dlg_inc_lock(dlg); return dlg; } @@ -292,16 +414,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) */ 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; + return buf_hash_add(pj_strbuf(str), pj_strlen(str), hash); } /*! @@ -340,7 +453,7 @@ struct ast_taskprocessor *ast_sip_get_distributor_serializer(pjsip_rx_data *rdat /* 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); + hash = ast_str_hash_restrict(hash); serializer = ao2_bump(distributor_pool[hash % ARRAY_LEN(distributor_pool)]); if (serializer) { @@ -375,17 +488,18 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) dlg = find_dialog(rdata); if (dlg) { - ast_debug(3, "Searching for serializer on dialog %s for %s\n", + ast_debug(3, "Searching for serializer associated with dialog %s for %s\n", dlg->obj_name, pjsip_rx_data_get_info(rdata)); - dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY); if (dist) { + ao2_lock(dist); serializer = ao2_bump(dist->serializer); + ao2_unlock(dist); if (serializer) { - ast_debug(3, "Found serializer %s on dialog %s\n", + ast_debug(3, "Found serializer %s associated with dialog %s\n", ast_taskprocessor_name(serializer), dlg->obj_name); } } - pjsip_dlg_dec_lock(dlg); } if (serializer) { @@ -407,6 +521,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) /* 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); + ao2_cleanup(dist); return PJ_TRUE; } else { if (ast_taskprocessor_alert_get()) { @@ -421,6 +536,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) */ ast_debug(3, "Taskprocessor overload alert: Ignoring '%s'.\n", pjsip_rx_data_get_info(rdata)); + ao2_cleanup(dist); return PJ_TRUE; } @@ -428,10 +544,17 @@ static pj_bool_t distributor(pjsip_rx_data *rdata) serializer = ast_sip_get_distributor_serializer(rdata); } - pjsip_rx_data_clone(rdata, 0, &clone); + if (pjsip_rx_data_clone(rdata, 0, &clone) != PJ_SUCCESS) { + ast_taskprocessor_unreference(serializer); + ao2_cleanup(dist); + return PJ_TRUE; + } if (dist) { + ao2_lock(dist); clone->endpt_info.mod_data[endpoint_mod.id] = ao2_bump(dist->endpoint); + ao2_unlock(dist); + ao2_cleanup(dist); } if (ast_sip_push_task(serializer, distribute, clone)) { @@ -827,7 +950,7 @@ static int suspects_compare(void *obj, void *arg, int flags) /* Fall through */ case OBJ_SEARCH_KEY: if (strcmp(object_left->src_name, right_key) == 0) { - cmp = CMP_MATCH | CMP_STOP; + cmp = CMP_MATCH; } break; case OBJ_SEARCH_PARTIAL_KEY: @@ -842,15 +965,25 @@ static int suspects_compare(void *obj, void *arg, int flags) return cmp; } -static int suspects_hash(const void *obj, int flags) { - const struct unidentified_request *object_left = obj; +static int suspects_hash(const void *obj, int flags) +{ + const struct unidentified_request *object; + const char *key; - if (flags & OBJ_SEARCH_OBJECT) { - return ast_str_hash(object_left->src_name); - } else if (flags & OBJ_SEARCH_KEY) { - return ast_str_hash(obj); + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key = obj; + break; + case OBJ_SEARCH_OBJECT: + object = obj; + key = object->src_name; + break; + default: + /* Hash can only work on something with a full key. */ + ast_assert(0); + return 0; } - return -1; + return ast_str_hash(key); } static struct ao2_container *cli_unid_get_container(const char *regex) @@ -1068,6 +1201,14 @@ int ast_sip_initialize_distributor(void) return -1; } + dialog_associations = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, + DIALOG_ASSOCIATIONS_BUCKETS, dialog_associations_hash, NULL, + dialog_associations_cmp); + if (!dialog_associations) { + ast_sip_destroy_distributor(); + return -1; + } + if (distributor_pool_setup()) { ast_sip_destroy_distributor(); return -1; @@ -1146,5 +1287,6 @@ void ast_sip_destroy_distributor(void) distributor_pool_shutdown(); + ao2_cleanup(dialog_associations); ao2_cleanup(unidentified_requests); } diff --git a/res/res_pjsip/pjsip_message_ip_updater.c b/res/res_pjsip/pjsip_message_ip_updater.c index 2d074640a..099ecaa66 100644 --- a/res/res_pjsip/pjsip_message_ip_updater.c +++ b/res/res_pjsip/pjsip_message_ip_updater.c @@ -153,7 +153,16 @@ static int multihomed_rewrite_sdp(struct pjmedia_sdp_session *sdp) return 0; } -static void sanitize_tdata(pjsip_tx_data *tdata) +#define is_sip_uri(uri) \ + (PJSIP_URI_SCHEME_IS_SIP(uri) || PJSIP_URI_SCHEME_IS_SIPS(uri)) + +#ifdef AST_DEVMODE +#define FUNC_ATTRS __attribute__ ((noinline)) +#else +#define FUNC_ATTRS +#endif + +static void FUNC_ATTRS sanitize_tdata(pjsip_tx_data *tdata) { static const pj_str_t x_name = { AST_SIP_X_AST_TXP, AST_SIP_X_AST_TXP_LEN }; pjsip_param *x_transport; @@ -161,29 +170,50 @@ static void sanitize_tdata(pjsip_tx_data *tdata) pjsip_fromto_hdr *fromto; pjsip_contact_hdr *contact; pjsip_hdr *hdr; +#ifdef AST_DEVMODE + char hdrbuf[512]; + int hdrbuf_len; +#endif if (tdata->msg->type == PJSIP_REQUEST_MSG) { - uri = pjsip_uri_get_uri(tdata->msg->line.req.uri); - x_transport = pjsip_param_find(&uri->other_param, &x_name); - if (x_transport) { - pj_list_erase(x_transport); + if (is_sip_uri(tdata->msg->line.req.uri)) { + uri = pjsip_uri_get_uri(tdata->msg->line.req.uri); +#ifdef AST_DEVMODE + hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, hdrbuf, 512); + ast_debug(2, "Sanitizing Request: %s\n", hdrbuf); +#endif + while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) { + pj_list_erase(x_transport); + } } } for (hdr = tdata->msg->hdr.next; hdr != &tdata->msg->hdr; hdr = hdr->next) { if (hdr->type == PJSIP_H_TO || hdr->type == PJSIP_H_FROM) { fromto = (pjsip_fromto_hdr *) hdr; - uri = pjsip_uri_get_uri(fromto->uri); - x_transport = pjsip_param_find(&uri->other_param, &x_name); - if (x_transport) { - pj_list_erase(x_transport); + if (is_sip_uri(fromto->uri)) { + uri = pjsip_uri_get_uri(fromto->uri); +#ifdef AST_DEVMODE + hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, uri, hdrbuf, 512); + hdrbuf[hdrbuf_len] = '\0'; + ast_debug(2, "Sanitizing From/To: %s\n", hdrbuf); +#endif + while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) { + pj_list_erase(x_transport); + } } } else if (hdr->type == PJSIP_H_CONTACT) { contact = (pjsip_contact_hdr *) hdr; - uri = pjsip_uri_get_uri(contact->uri); - x_transport = pjsip_param_find(&uri->other_param, &x_name); - if (x_transport) { - pj_list_erase(x_transport); + if (is_sip_uri(contact->uri)) { + uri = pjsip_uri_get_uri(contact->uri); +#ifdef AST_DEVMODE + hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, uri, hdrbuf, 512); + hdrbuf[hdrbuf_len] = '\0'; + ast_debug(2, "Sanitizing Contact: %s\n", hdrbuf); +#endif + while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) { + pj_list_erase(x_transport); + } } } } diff --git a/res/res_pjsip/pjsip_transport_events.c b/res/res_pjsip/pjsip_transport_events.c new file mode 100644 index 000000000..0f57303ba --- /dev/null +++ b/res/res_pjsip/pjsip_transport_events.c @@ -0,0 +1,366 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2017, Digium Inc. + * + * Richard Mudgett <rmudgett@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 Manages the global transport event notification callbacks. + * + * \author Richard Mudgett <rmudgett@digium.com> + * See Also: + * + * \arg \ref AstCREDITS + */ + + +#include "asterisk.h" + +#include "asterisk/res_pjsip.h" +#include "include/res_pjsip_private.h" +#include "asterisk/linkedlists.h" +#include "asterisk/vector.h" + +/* ------------------------------------------------------------------- */ + +/*! \brief Number of buckets for monitored active transports */ +#define ACTIVE_TRANSPORTS_BUCKETS 127 + +/*! Who to notify when transport shuts down. */ +struct transport_monitor_notifier { + /*! Who to call when transport shuts down. */ + ast_transport_monitor_shutdown_cb cb; + /*! ao2 data object to pass to callback. */ + void *data; +}; + +/*! \brief Structure for transport to be monitored */ +struct transport_monitor { + /*! \brief The underlying PJSIP transport */ + pjsip_transport *transport; + /*! Who is interested in when this transport shuts down. */ + AST_VECTOR(, struct transport_monitor_notifier) monitors; +}; + +/*! \brief Global container of active reliable transports */ +static AO2_GLOBAL_OBJ_STATIC(active_transports); + +/*! \brief Existing transport events callback that we need to invoke */ +static pjsip_tp_state_callback tpmgr_state_callback; + +/*! List of registered transport state callbacks. */ +static AST_RWLIST_HEAD(, ast_sip_tpmgr_state_callback) transport_state_list; + + +/*! \brief Hashing function for struct transport_monitor */ +AO2_STRING_FIELD_HASH_FN(transport_monitor, transport->obj_name); + +/*! \brief Comparison function for struct transport_monitor */ +AO2_STRING_FIELD_CMP_FN(transport_monitor, transport->obj_name); + +static const char *transport_state2str(pjsip_transport_state state) +{ + const char *name; + + switch (state) { + case PJSIP_TP_STATE_CONNECTED: + name = "CONNECTED"; + break; + case PJSIP_TP_STATE_DISCONNECTED: + name = "DISCONNECTED"; + break; + case PJSIP_TP_STATE_SHUTDOWN: + name = "SHUTDOWN"; + break; + case PJSIP_TP_STATE_DESTROY: + name = "DESTROY"; + break; + default: + /* + * We have to have a default case because the enum is + * defined by a third-party library. + */ + ast_assert(0); + name = "<unknown>"; + break; + } + return name; +} + +static void transport_monitor_dtor(void *vdoomed) +{ + struct transport_monitor *monitored = vdoomed; + int idx; + + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + ao2_cleanup(notifier->data); + } + AST_VECTOR_FREE(&monitored->monitors); +} + +/*! \brief Callback invoked when transport state changes occur */ +static void transport_state_callback(pjsip_transport *transport, + pjsip_transport_state state, const pjsip_transport_state_info *info) +{ + struct ao2_container *transports; + + /* We only care about monitoring reliable transports */ + if (PJSIP_TRANSPORT_IS_RELIABLE(transport) + && (transports = ao2_global_obj_ref(active_transports))) { + struct transport_monitor *monitored; + + ast_debug(3, "Reliable transport '%s' state:%s\n", + transport->obj_name, transport_state2str(state)); + switch (state) { + case PJSIP_TP_STATE_CONNECTED: + monitored = ao2_alloc_options(sizeof(*monitored), + transport_monitor_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!monitored) { + break; + } + monitored->transport = transport; + if (AST_VECTOR_INIT(&monitored->monitors, 2)) { + ao2_ref(monitored, -1); + break; + } + + ao2_link(transports, monitored); + ao2_ref(monitored, -1); + break; + case PJSIP_TP_STATE_DISCONNECTED: + if (!transport->is_shutdown) { + pjsip_transport_shutdown(transport); + } + break; + case PJSIP_TP_STATE_SHUTDOWN: + /* + * Set shutdown flag early so we can force a new transport to be + * created if a monitor callback needs to reestablish a link. + * PJPROJECT sets the flag after this routine returns even though + * it has already called the transport's shutdown routine. + */ + transport->is_shutdown = PJ_TRUE; + + monitored = ao2_find(transports, transport->obj_name, + OBJ_SEARCH_KEY | OBJ_UNLINK); + if (monitored) { + int idx; + + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + notifier->cb(notifier->data); + } + ao2_ref(monitored, -1); + } + break; + default: + break; + } + + ao2_ref(transports, -1); + } + + /* Loop over other transport state callbacks registered with us. */ + if (!AST_LIST_EMPTY(&transport_state_list)) { + struct ast_sip_tpmgr_state_callback *tpmgr_notifier; + + AST_RWLIST_RDLOCK(&transport_state_list); + AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) { + tpmgr_notifier->cb(transport, state, info); + } + AST_RWLIST_UNLOCK(&transport_state_list); + } + + /* Forward to the old state callback if present */ + if (tpmgr_state_callback) { + tpmgr_state_callback(transport, state, info); + } +} + +static int transport_monitor_unregister_all(void *obj, void *arg, int flags) +{ + struct transport_monitor *monitored = obj; + ast_transport_monitor_shutdown_cb cb = arg; + int idx; + + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + if (notifier->cb == cb) { + ao2_cleanup(notifier->data); + AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx); + break; + } + } + return 0; +} + +void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb cb) +{ + struct ao2_container *transports; + + transports = ao2_global_obj_ref(active_transports); + if (!transports) { + return; + } + ao2_callback(transports, OBJ_MULTIPLE | OBJ_NODATA, transport_monitor_unregister_all, + cb); + ao2_ref(transports, -1); +} + +void ast_sip_transport_monitor_unregister(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb) +{ + struct ao2_container *transports; + struct transport_monitor *monitored; + + transports = ao2_global_obj_ref(active_transports); + if (!transports) { + return; + } + + ao2_lock(transports); + monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (monitored) { + int idx; + + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + if (notifier->cb == cb) { + ao2_cleanup(notifier->data); + AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx); + break; + } + } + ao2_ref(monitored, -1); + } + ao2_unlock(transports); + ao2_ref(transports, -1); +} + +enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport, + ast_transport_monitor_shutdown_cb cb, void *ao2_data) +{ + struct ao2_container *transports; + struct transport_monitor *monitored; + enum ast_transport_monitor_reg res = AST_TRANSPORT_MONITOR_REG_NOT_FOUND; + + transports = ao2_global_obj_ref(active_transports); + if (!transports) { + return res; + } + + ao2_lock(transports); + monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (monitored) { + int idx; + struct transport_monitor_notifier new_monitor; + + /* Check if the callback monitor already exists */ + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + if (notifier->cb == cb) { + /* The monitor is already in the vector replace with new ao2_data. */ + ao2_replace(notifier->data, ao2_data); + res = AST_TRANSPORT_MONITOR_REG_REPLACED; + goto register_done; + } + } + + /* Add new monitor to vector */ + new_monitor.cb = cb; + new_monitor.data = ao2_bump(ao2_data); + if (AST_VECTOR_APPEND(&monitored->monitors, new_monitor)) { + ao2_cleanup(ao2_data); + res = AST_TRANSPORT_MONITOR_REG_FAILED; + } + +register_done: + ao2_ref(monitored, -1); + } + ao2_unlock(transports); + ao2_ref(transports, -1); + return res; +} + +void ast_sip_transport_state_unregister(struct ast_sip_tpmgr_state_callback *element) +{ + AST_RWLIST_WRLOCK(&transport_state_list); + AST_LIST_REMOVE(&transport_state_list, element, node); + AST_RWLIST_UNLOCK(&transport_state_list); +} + +void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *element) +{ + struct ast_sip_tpmgr_state_callback *tpmgr_notifier; + + AST_RWLIST_WRLOCK(&transport_state_list); + AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) { + if (element == tpmgr_notifier) { + /* Already registered. */ + AST_RWLIST_UNLOCK(&transport_state_list); + return; + } + } + AST_LIST_INSERT_HEAD(&transport_state_list, element, node); + AST_RWLIST_UNLOCK(&transport_state_list); +} + +void ast_sip_destroy_transport_events(void) +{ + pjsip_tpmgr *tpmgr; + + tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + if (tpmgr) { + pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback); + } + + ao2_global_obj_release(active_transports); +} + +int ast_sip_initialize_transport_events(void) +{ + pjsip_tpmgr *tpmgr; + struct ao2_container *transports; + + tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + if (!tpmgr) { + return -1; + } + + transports = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, + ACTIVE_TRANSPORTS_BUCKETS, transport_monitor_hash_fn, NULL, + transport_monitor_cmp_fn); + if (!transports) { + return -1; + } + ao2_global_obj_replace_unref(active_transports, transports); + ao2_ref(transports, -1); + + tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr); + pjsip_tpmgr_set_state_cb(tpmgr, &transport_state_callback); + + return 0; +} diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c index c991a0d68..3cea79e98 100644 --- a/res/res_pjsip/presence_xml.c +++ b/res/res_pjsip/presence_xml.c @@ -82,43 +82,48 @@ void ast_sip_sanitize_xml(const char *input, char *output, size_t len) } void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **pidfstate, - char **pidfnote, enum ast_sip_pidf_state *local_state) + char **pidfnote, enum ast_sip_pidf_state *local_state, + unsigned int notify_early_inuse_ringing) { switch (state) { case AST_EXTENSION_RINGING: *statestring = "early"; *local_state = NOTIFY_INUSE; - *pidfstate = "busy"; + *pidfstate = "on-the-phone"; *pidfnote = "Ringing"; break; case (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING): - *statestring = "confirmed"; + if (notify_early_inuse_ringing) { + *statestring = "early"; + } else { + *statestring = "confirmed"; + } *local_state = NOTIFY_INUSE; - *pidfstate = "busy"; + *pidfstate = "on-the-phone"; *pidfnote = "Ringing"; break; case AST_EXTENSION_INUSE: *statestring = "confirmed"; *local_state = NOTIFY_INUSE; - *pidfstate = "busy"; + *pidfstate = "on-the-phone"; *pidfnote = "On the phone"; break; case AST_EXTENSION_BUSY: *statestring = "confirmed"; - *local_state = NOTIFY_CLOSED; - *pidfstate = "busy"; + *local_state = NOTIFY_INUSE; + *pidfstate = "on-the-phone"; *pidfnote = "On the phone"; break; case AST_EXTENSION_UNAVAILABLE: *statestring = "terminated"; *local_state = NOTIFY_CLOSED; - *pidfstate = "away"; + *pidfstate = "--"; *pidfnote = "Unavailable"; break; case AST_EXTENSION_ONHOLD: *statestring = "confirmed"; - *local_state = NOTIFY_CLOSED; - *pidfstate = "busy"; + *local_state = NOTIFY_INUSE; + *pidfstate = "on-the-phone"; *pidfnote = "On hold"; break; case AST_EXTENSION_NOT_INUSE: diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c index b21b70fb1..fa3d710e5 100644 --- a/res/res_pjsip_dialog_info_body_generator.c +++ b/res/res_pjsip_dialog_info_body_generator.c @@ -107,6 +107,8 @@ static int dialog_info_generate_body_content(void *body, void *data) enum ast_sip_pidf_state local_state; unsigned int version; char version_str[32], sanitized[PJSIP_MAX_URL_SIZE]; + struct ast_sip_endpoint *endpoint = NULL; + unsigned int notify_early_inuse_ringing = 0; if (!local || !state_data->sub) { return -1; @@ -120,8 +122,12 @@ static int dialog_info_generate_body_content(void *body, void *data) stripped = ast_strip_quoted(local, "<", ">"); ast_sip_sanitize_xml(stripped, sanitized, sizeof(sanitized)); + if (state_data->sub && (endpoint = ast_sip_subscription_get_endpoint(state_data->sub))) { + notify_early_inuse_ringing = endpoint->notify_early_inuse_ringing; + ao2_cleanup(endpoint); + } ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, - &pidfstate, &pidfnote, &local_state); + &pidfstate, &pidfnote, &local_state, notify_early_inuse_ringing); ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "xmlns", "urn:ietf:params:xml:ns:dialog-info"); @@ -133,7 +139,7 @@ static int dialog_info_generate_body_content(void *body, void *data) dialog = ast_sip_presence_xml_create_node(state_data->pool, dialog_info, "dialog"); ast_sip_presence_xml_create_attr(state_data->pool, dialog, "id", state_data->exten); - if (state_data->exten_state == AST_EXTENSION_RINGING) { + if (!ast_strlen_zero(statestring) && !strcmp(statestring, "early")) { ast_sip_presence_xml_create_attr(state_data->pool, dialog, "direction", "recipient"); } diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c index 8b465e007..2f01a0f47 100644 --- a/res/res_pjsip_messaging.c +++ b/res/res_pjsip_messaging.c @@ -512,7 +512,7 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as buf[size] = '\0'; res |= ast_msg_set_from(msg, "%s", buf); - field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf) - 1, 1); + field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf) - 1, 3); res |= ast_msg_set_var(msg, "PJSIP_RECVADDR", field); switch (rdata->tp_info.transport->key.type) { diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c index 5ae2af5d3..05eee782e 100644 --- a/res/res_pjsip_mwi.c +++ b/res/res_pjsip_mwi.c @@ -1102,6 +1102,13 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags } if (endpoint->subscription.mwi.aggregate) { + const char *endpoint_id = ast_sorcery_object_get_id(endpoint); + + /* Check if subscription exists */ + aggregate_sub = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (aggregate_sub) { + return 0; + } aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL); if (!aggregate_sub) { return 0; @@ -1113,7 +1120,9 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags struct mwi_subscription *sub; struct mwi_stasis_subscription *mwi_stasis_sub; - if (ast_strlen_zero(mailbox)) { + /* check if subscription exists */ + if (ast_strlen_zero(mailbox) || + (!aggregate_sub && endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox))) { continue; } @@ -1189,31 +1198,79 @@ static int send_contact_notify(void *obj, void *arg, int flags) return 0; } -/*! \brief Function called when a contact is updated */ -static void mwi_contact_updated(const void *object) +/*! \brief Create mwi subscriptions and notify */ +static void mwi_contact_changed(const struct ast_sip_contact *contact) { - char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL; + char *id = ast_strdupa(ast_sorcery_object_get_id(contact)); + char *aor = NULL; + struct ast_sip_endpoint *endpoint = NULL; - aor = strsep(&id, ";@"); + if (contact->endpoint) { + endpoint = ao2_bump(contact->endpoint); + } else { + if (!ast_strlen_zero(contact->endpoint_name)) { + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name); + } + } + if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) { + ao2_cleanup(endpoint); + return; + } + + ao2_lock(unsolicited_mwi); + create_mwi_subscriptions_for_endpoint(endpoint, NULL, 0); + ao2_unlock(unsolicited_mwi); + ao2_cleanup(endpoint); + + aor = strsep(&id, ";@"); ao2_callback(unsolicited_mwi, OBJ_NODATA, send_contact_notify, aor); } +/*! \brief Function called when a contact is updated */ +static void mwi_contact_updated(const void *object) +{ + mwi_contact_changed(object); +} + /*! \brief Function called when a contact is added */ static void mwi_contact_added(const void *object) { + mwi_contact_changed(object); +} + +/*! \brief Function called when a contact is deleted */ +static void mwi_contact_deleted(const void *object) +{ const struct ast_sip_contact *contact = object; struct ao2_iterator *mwi_subs; struct mwi_subscription *mwi_sub; - const char *endpoint_id = ast_sorcery_object_get_id(contact->endpoint); + struct ast_sip_endpoint *endpoint = NULL; + struct ast_sip_contact *found_contact; - if (ast_strlen_zero(contact->endpoint->subscription.mwi.mailboxes)) { + if (contact->endpoint) { + endpoint = ao2_bump(contact->endpoint); + } else { + if (!ast_strlen_zero(contact->endpoint_name)) { + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name); + } + } + + if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) { + ao2_cleanup(endpoint); return; } - ao2_lock(unsolicited_mwi); + /* Check if there is another contact */ + found_contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors); + ao2_cleanup(endpoint); + if (found_contact) { + ao2_cleanup(found_contact); + return; + } - mwi_subs = ao2_find(unsolicited_mwi, endpoint_id, + ao2_lock(unsolicited_mwi); + mwi_subs = ao2_find(unsolicited_mwi, contact->endpoint_name, OBJ_SEARCH_KEY | OBJ_MULTIPLE | OBJ_NOLOCK | OBJ_UNLINK); if (mwi_subs) { for (; (mwi_sub = ao2_iterator_next(mwi_subs)); ao2_cleanup(mwi_sub)) { @@ -1221,18 +1278,14 @@ static void mwi_contact_added(const void *object) } ao2_iterator_destroy(mwi_subs); } - - create_mwi_subscriptions_for_endpoint(contact->endpoint, NULL, 0); - ao2_unlock(unsolicited_mwi); - - mwi_contact_updated(object); } /*! \brief Observer for contacts so unsolicited MWI is sent when a contact changes */ static const struct ast_sorcery_observer mwi_contact_observer = { .created = mwi_contact_added, .updated = mwi_contact_updated, + .deleted = mwi_contact_deleted, }; /*! \brief Task invoked to send initial MWI NOTIFY for unsolicited */ @@ -1278,7 +1331,9 @@ static struct ast_sorcery_observer global_observer = { static int reload(void) { - create_mwi_subscriptions(); + if (!ast_sip_get_mwi_disable_initial_unsolicited()) { + create_mwi_subscriptions(); + } return 0; } @@ -1301,13 +1356,13 @@ static int load_module(void) ast_sip_unregister_subscription_handler(&mwi_handler); return AST_MODULE_LOAD_DECLINE; } - create_mwi_subscriptions(); ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &mwi_contact_observer); ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer); ast_sorcery_reload_object(ast_sip_get_sorcery(), "global"); if (!ast_sip_get_mwi_disable_initial_unsolicited()) { + create_mwi_subscriptions(); if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) { ast_sip_push_task(NULL, send_initial_notify_all, NULL); } else { diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c index a0ce2a9d1..370004a3a 100644 --- a/res/res_pjsip_nat.c +++ b/res/res_pjsip_nat.c @@ -35,6 +35,7 @@ static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri) { pj_cstr(&uri->host, rdata->pkt_info.src_name); + uri->port = rdata->pkt_info.src_port; if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) { /* WSS is special, we don't want to overwrite the URI at all as it needs to be ws */ } else if (strcasecmp("udp", rdata->tp_info.transport->type_name)) { @@ -42,7 +43,6 @@ static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri) } else { uri->transport_param.slen = 0; } - uri->port = rdata->pkt_info.src_port; } static int rewrite_route_set(pjsip_rx_data *rdata, pjsip_dialog *dlg) @@ -165,7 +165,7 @@ static int find_transport_state_in_use(void *obj, void *arg, int flags) ((details->type == transport_state->type) && (transport_state->factory) && !pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) && transport_state->factory->addr_name.port == details->local_port))) { - return CMP_MATCH | CMP_STOP; + return CMP_MATCH; } return 0; @@ -267,16 +267,16 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata) ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port); /* See if where we are sending this request is local or not, and if not that we can get a Contact URI to modify */ - if (ast_apply_ha(transport_state->localnet, &addr) != AST_SENSE_ALLOW) { + if (ast_sip_transport_is_local(transport_state, &addr)) { ast_debug(5, "Request is being sent to local address, skipping NAT manipulation\n"); return PJ_SUCCESS; } } - if (!ast_sockaddr_isnull(&transport_state->external_address)) { + if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) { /* Update the contact header with the external address */ if (uri || (uri = nat_get_contact_sip_uri(tdata))) { - pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_address)); + pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address)); if (transport->external_signaling_port) { uri->port = transport->external_signaling_port; ast_debug(4, "Re-wrote Contact URI port to %d\n", uri->port); @@ -285,7 +285,7 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata) /* Update the via header if relevant */ if ((tdata->msg->type == PJSIP_REQUEST_MSG) && (via || (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL)))) { - pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_address)); + pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address)); if (transport->external_signaling_port) { via->sent_by.port = transport->external_signaling_port; } diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c index 0a65e6e1d..76d756d9e 100644 --- a/res/res_pjsip_outbound_registration.c +++ b/res/res_pjsip_outbound_registration.c @@ -358,6 +358,8 @@ struct sip_outbound_registration_client_state { unsigned int auth_attempted:1; /*! \brief The name of the transport to be used for the registration */ char *transport_name; + /*! \brief The name of the registration sorcery object */ + char *registration_name; }; /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */ @@ -459,7 +461,7 @@ static int line_identify_relationship(void *obj, void *arg, int flags) struct sip_outbound_registration_state *state = obj; pjsip_param *line = arg; - return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH | CMP_STOP : 0; + return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH : 0; } static struct pjsip_param *get_uri_option_line(const void *uri) @@ -559,20 +561,21 @@ static int handle_client_registration(void *data) { RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup); pjsip_tx_data *tdata; - pjsip_regc_info info; - char server_uri[PJSIP_MAX_URL_SIZE]; - char client_uri[PJSIP_MAX_URL_SIZE]; if (client_state->status == SIP_REGISTRATION_STOPPED || pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) { return 0; } - pjsip_regc_get_info(client_state->client, &info); - ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri)); - ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri)); - ast_debug(1, "Outbound REGISTER attempt %u to '%s' with client '%s'\n", - client_state->retries + 1, server_uri, client_uri); + if (DEBUG_ATLEAST(1)) { + pjsip_regc_info info; + + pjsip_regc_get_info(client_state->client, &info); + ast_log(LOG_DEBUG, "Outbound REGISTER attempt %u to '%.*s' with client '%.*s'\n", + client_state->retries + 1, + (int) info.server_uri.slen, info.server_uri.ptr, + (int) info.client_uri.slen, info.client_uri.ptr); + } if (client_state->support_path) { pjsip_supported_hdr *hdr; @@ -795,6 +798,82 @@ static void schedule_retry(struct registration_response *response, unsigned int } } +static int reregister_immediately_cb(void *obj) +{ + struct sip_outbound_registration_state *state = obj; + + if (state->client_state->status != SIP_REGISTRATION_REGISTERED) { + ao2_ref(state, -1); + return 0; + } + + if (DEBUG_ATLEAST(1)) { + pjsip_regc_info info; + + pjsip_regc_get_info(state->client_state->client, &info); + ast_log(LOG_DEBUG, + "Outbound registration transport to server '%.*s' from client '%.*s' shutdown\n", + (int) info.server_uri.slen, info.server_uri.ptr, + (int) info.client_uri.slen, info.client_uri.ptr); + } + + cancel_registration(state->client_state); + + ao2_ref(state->client_state, +1); + handle_client_registration(state->client_state); + + ao2_ref(state, -1); + return 0; +} + +/*! + * \internal + * \brief The reliable transport we registered using has shutdown. + * \since 13.18.0 + * + * \param obj What is needed to initiate a reregister attempt. + * + * \return Nothing + */ +static void registration_transport_shutdown_cb(void *obj) +{ + const char *registration_name = obj; + struct sip_outbound_registration_state *state; + + state = get_state(registration_name); + if (!state) { + /* Registration no longer exists or shutting down. */ + return; + } + if (ast_sip_push_task(state->client_state->serializer, reregister_immediately_cb, state)) { + ao2_ref(state, -1); + } +} + +static void registration_transport_monitor_setup(pjsip_transport *transport, const char *registration_name) +{ + char *monitor; + + if (!PJSIP_TRANSPORT_IS_RELIABLE(transport)) { + return; + } + monitor = ao2_alloc_options(strlen(registration_name) + 1, NULL, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!monitor) { + return; + } + strcpy(monitor, registration_name);/* Safe */ + + /* + * We'll ignore if the transport has already been shutdown before we + * register the monitor. We might get into a message spamming infinite + * loop of registration, shutdown, reregistration... + */ + ast_sip_transport_monitor_register(transport, registration_transport_shutdown_cb, + monitor); + ao2_ref(monitor, -1); +} + /*! \brief Callback function for handling a response to a registration attempt */ static int handle_registration_response(void *data) { @@ -863,9 +942,15 @@ static int handle_registration_response(void *data) next_registration_round = 0; } schedule_registration(response->client_state, next_registration_round); + + /* See if we should monitor for transport shutdown */ + registration_transport_monitor_setup(response->rdata->tp_info.transport, + response->client_state->registration_name); } else { ast_debug(1, "Outbound unregistration to '%s' with client '%s' successful\n", server_uri, client_uri); update_client_state_status(response->client_state, SIP_REGISTRATION_UNREGISTERED); + ast_sip_transport_monitor_unregister(response->rdata->tp_info.transport, + registration_transport_shutdown_cb); } } else if (response->client_state->destroy) { /* We need to deal with the pending destruction instead. */ @@ -988,7 +1073,8 @@ static void sip_outbound_registration_state_destroy(void *obj) struct sip_outbound_registration_state *state = obj; ast_debug(3, "Destroying registration state for registration to server '%s' from client '%s'\n", - state->registration->server_uri, state->registration->client_uri); + state->registration ? state->registration->server_uri : "", + state->registration ? state->registration->client_uri : ""); ao2_cleanup(state->registration); if (!state->client_state) { @@ -1007,12 +1093,13 @@ static void sip_outbound_registration_client_state_destroy(void *obj) { struct sip_outbound_registration_client_state *client_state = obj; - ast_free(client_state->transport_name); ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "-1", 1.0); ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0, sip_outbound_registration_status_str(client_state->status)); ast_taskprocessor_unreference(client_state->serializer); + ast_free(client_state->transport_name); + ast_free(client_state->registration_name); } /*! \brief Allocator function for registration state */ @@ -1032,6 +1119,23 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a return NULL; } + state->client_state->status = SIP_REGISTRATION_UNREGISTERED; + state->client_state->timer.user_data = state->client_state; + state->client_state->timer.cb = sip_outbound_registration_timer_cb; + state->client_state->transport_name = ast_strdup(registration->transport); + state->client_state->registration_name = + ast_strdup(ast_sorcery_object_get_id(registration)); + + ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0); + ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0, + sip_outbound_registration_status_str(state->client_state->status)); + + if (!state->client_state->transport_name + || !state->client_state->registration_name) { + ao2_cleanup(state); + return NULL; + } + /* Create name with seq number appended. */ ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outreg/%s", ast_sorcery_object_get_id(registration)); @@ -1042,14 +1146,6 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a ao2_cleanup(state); return NULL; } - state->client_state->status = SIP_REGISTRATION_UNREGISTERED; - state->client_state->timer.user_data = state->client_state; - state->client_state->timer.cb = sip_outbound_registration_timer_cb; - state->client_state->transport_name = ast_strdup(registration->transport); - - ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0); - ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0, - sip_outbound_registration_status_str(state->client_state->status)); state->registration = ao2_bump(registration); return state; @@ -2053,6 +2149,8 @@ static int unload_module(void) ao2_global_obj_release(current_states); + ast_sip_transport_monitor_unregister_all(registration_transport_shutdown_cb); + /* Wait for registration serializers to get destroyed. */ ast_debug(2, "Waiting for registration transactions to complete for unload.\n"); remaining = ast_serializer_shutdown_group_join(shutdown_group, MAX_UNLOAD_TIMEOUT_TIME); diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c index d3be8c131..4daff964d 100644 --- a/res/res_pjsip_pidf_body_generator.c +++ b/res/res_pjsip_pidf_body_generator.c @@ -58,7 +58,7 @@ static int pidf_generate_body_content(void *body, void *data) struct ast_sip_exten_state_data *state_data = data; ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, - &pidfstate, &pidfnote, &local_state); + &pidfstate, &pidfnote, &local_state, 0); if (!pjpidf_pres_add_note(state_data->pool, pres, pj_cstr(¬e, pidfnote))) { ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n"); @@ -75,7 +75,7 @@ static int pidf_generate_body_content(void *body, void *data) pjpidf_tuple_set_contact(state_data->pool, tuple, pj_cstr(&contact, sanitized)); pjpidf_tuple_set_contact_prio(state_data->pool, tuple, pj_cstr(&priority, "1")); pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple), - local_state == NOTIFY_OPEN); + local_state == NOTIFY_OPEN || local_state == NOTIFY_INUSE); return 0; } diff --git a/res/res_pjsip_pidf_eyebeam_body_supplement.c b/res/res_pjsip_pidf_eyebeam_body_supplement.c index cd590c3d3..0200a4654 100644 --- a/res/res_pjsip_pidf_eyebeam_body_supplement.c +++ b/res/res_pjsip_pidf_eyebeam_body_supplement.c @@ -46,30 +46,28 @@ */ static void add_eyebeam(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate) { - static const char *XMLNS_PP = "xmlns:pp"; - static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person"; + static const char *XMLNS_DM_PREFIX = "xmlns:dm"; + static const char *XMLNS_DM = "urn:ietf:params:xml:ns:pidf:data-model"; - static const char *XMLNS_ES = "xmlns:es"; - static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status"; + static const char *XMLNS_RPID_PREFIX = "xmlns:rpid"; + static const char *XMLNS_RPID = "urn:ietf:params:xml:ns:pidf:rpid"; - static const char *XMLNS_EP = "xmlns:ep"; - static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person"; - - pj_xml_node *person = ast_sip_presence_xml_create_node(pool, node, "pp:person"); - pj_xml_node *status = ast_sip_presence_xml_create_node(pool, person, "status"); + pj_xml_node *person = ast_sip_presence_xml_create_node(pool, node, "dm:person"); if (pidfstate[0] != '-') { - pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, status, "ep:activities"); - size_t str_size = sizeof("ep:") + strlen(pidfstate); + pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, person, "rpid:activities"); + size_t str_size = sizeof("rpid:") + strlen(pidfstate); + char *act_str = ast_alloca(str_size); + + /* Safe */ + strcpy(act_str, "rpid:"); + strcat(act_str, pidfstate); - activities->content.ptr = pj_pool_alloc(pool, str_size); - activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size, - "ep:%s", pidfstate); + ast_sip_presence_xml_create_node(pool, activities, act_str); } - ast_sip_presence_xml_create_attr(pool, node, XMLNS_PP, XMLNS_PERSON); - ast_sip_presence_xml_create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS); - ast_sip_presence_xml_create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON); + ast_sip_presence_xml_create_attr(pool, node, XMLNS_DM_PREFIX, XMLNS_DM); + ast_sip_presence_xml_create_attr(pool, node, XMLNS_RPID_PREFIX, XMLNS_RPID); } static int pidf_supplement_body(void *body, void *data) @@ -80,7 +78,7 @@ static int pidf_supplement_body(void *body, void *data) enum ast_sip_pidf_state local_state; ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, - &pidfstate, &pidfnote, &local_state); + &pidfstate, &pidfnote, &local_state, 0); add_eyebeam(state_data->pool, pres, pidfstate); return 0; diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index fbb1ad4e8..c62bddd56 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -1108,7 +1108,9 @@ static void remove_subscription(struct sip_subscription_tree *obj) static void destroy_subscription(struct ast_sip_subscription *sub) { ast_debug(3, "Destroying SIP subscription from '%s->%s'\n", - ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource); + sub->tree->endpoint ? ast_sorcery_object_get_id(sub->tree->endpoint) : "Unknown", + sub->resource); + ast_free(sub->body_text); AST_VECTOR_FREE(&sub->children); @@ -1265,14 +1267,14 @@ static void subscription_tree_destructor(void *obj) sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown", sub_tree->root ? sub_tree->root->resource : "Unknown"); - ao2_cleanup(sub_tree->endpoint); - destroy_subscriptions(sub_tree->root); if (sub_tree->dlg) { ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree); } + ao2_cleanup(sub_tree->endpoint); + ast_taskprocessor_unreference(sub_tree->serializer); ast_module_unref(ast_module_info->self); } diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c index 111c4b2a1..ff8bc6a66 100644 --- a/res/res_pjsip_refer.c +++ b/res/res_pjsip_refer.c @@ -543,6 +543,7 @@ static int refer_attended_task(void *data) } } + ast_sip_session_end_if_deferred(attended->transferer); if (response != 200) { if (!ast_sip_push_task(attended->transferer->serializer, defer_termination_cancel, attended->transferer)) { @@ -772,6 +773,7 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi /* Push it to the other session, which will have both channels with minimal locking */ if (ast_sip_push_task(other_session->serializer, refer_attended_task, attended)) { + ast_sip_session_end_if_deferred(session); ast_sip_session_defer_termination_cancel(session); ao2_cleanup(attended); return 500; @@ -810,9 +812,12 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel, "external_replaces", context, refer_blind_callback, &refer)); + + ast_sip_session_end_if_deferred(session); if (response != 200) { ast_sip_session_defer_termination_cancel(session); } + return response; } } @@ -865,9 +870,12 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel, exten, context, refer_blind_callback, &refer)); + + ast_sip_session_end_if_deferred(session); if (response != 200) { ast_sip_session_defer_termination_cancel(session); } + return response; } diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index d54bffa0c..48b48356c 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -123,7 +123,7 @@ static int registrar_find_contact(void *obj, void *arg, int flags) const struct registrar_contact_details *details = arg; pjsip_uri *contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0); - return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH | CMP_STOP : 0; + return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH : 0; } /*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */ @@ -310,6 +310,47 @@ static int registrar_validate_path(pjsip_rx_data *rdata, struct ast_sip_aor *aor return -1; } +/*! Transport monitor for incoming REGISTER contacts */ +struct contact_transport_monitor { + /*! + * \brief Sorcery contact name to remove on transport shutdown + * \note Stored after aor_name in space reserved when struct allocated. + */ + char *contact_name; + /*! AOR name the contact is associated */ + char aor_name[0]; +}; + +static void register_contact_transport_shutdown_cb(void *data) +{ + struct contact_transport_monitor *monitor = data; + struct ast_sip_contact *contact; + struct ast_named_lock *lock; + + lock = ast_named_lock_get(AST_NAMED_LOCK_TYPE_MUTEX, "aor", monitor->aor_name); + if (!lock) { + return; + } + + ao2_lock(lock); + contact = ast_sip_location_retrieve_contact(monitor->contact_name); + if (contact) { + ast_sip_location_delete_contact(contact); + ast_verb(3, "Removed contact '%s' from AOR '%s' due to transport shutdown\n", + contact->uri, monitor->aor_name); + ast_test_suite_event_notify("AOR_CONTACT_REMOVED", + "Contact: %s\r\n" + "AOR: %s\r\n" + "UserAgent: %s", + contact->uri, + monitor->aor_name, + contact->user_agent); + ao2_ref(contact, -1); + } + ao2_unlock(lock); + ast_named_lock_put(lock); +} + static int register_aor_core(pjsip_rx_data *rdata, struct ast_sip_endpoint *endpoint, struct ast_sip_aor *aor, @@ -419,6 +460,9 @@ static int register_aor_core(pjsip_rx_data *rdata, pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)); if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) { + int prune_on_boot = 0; + pj_str_t host_name; + /* If they are actually trying to delete a contact that does not exist... be forgiving */ if (!expiration) { ast_verb(3, "Attempted to remove non-existent contact '%s' from AOR '%s' by request\n", @@ -426,14 +470,68 @@ static int register_aor_core(pjsip_rx_data *rdata, continue; } - 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, endpoint)) { + /* Determine if the contact cannot survive a restart/boot. */ + if (details.uri->port == rdata->pkt_info.src_port + && !pj_strcmp(&details.uri->host, + pj_cstr(&host_name, rdata->pkt_info.src_name)) + /* We have already checked if the URI scheme is sip: or sips: */ + && PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport)) { + pj_str_t type_name; + + /* Determine the transport parameter value */ + if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) { + /* WSS is special, as it needs to be ws. */ + pj_cstr(&type_name, "ws"); + } else { + pj_cstr(&type_name, rdata->tp_info.transport->type_name); + } + + if (!pj_stricmp(&details.uri->transport_param, &type_name) + && (endpoint->nat.rewrite_contact + /* Websockets are always rewritten */ + || !pj_stricmp(&details.uri->transport_param, + pj_cstr(&type_name, "ws")))) { + /* + * The contact was rewritten to the reliable transport's + * source address. Disconnecting the transport for any + * reason invalidates the contact. + */ + prune_on_boot = 1; + } + } + + contact = ast_sip_location_create_contact(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, prune_on_boot, endpoint); + if (!contact) { ast_log(LOG_ERROR, "Unable to bind contact '%s' to AOR '%s'\n", - contact_uri, aor_name); + contact_uri, aor_name); continue; } + if (prune_on_boot) { + const char *contact_name; + struct contact_transport_monitor *monitor; + + /* + * Monitor the transport in case it gets disconnected because + * the contact won't be valid anymore if that happens. + */ + contact_name = ast_sorcery_object_get_id(contact); + monitor = ao2_alloc_options(sizeof(*monitor) + 2 + strlen(aor_name) + + strlen(contact_name), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (monitor) { + strcpy(monitor->aor_name, aor_name);/* Safe */ + monitor->contact_name = monitor->aor_name + strlen(aor_name) + 1; + strcpy(monitor->contact_name, contact_name);/* Safe */ + + ast_sip_transport_monitor_register(rdata->tp_info.transport, + register_contact_transport_shutdown_cb, monitor); + ao2_ref(monitor, -1); + } + } + ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n", contact_uri, aor_name, expiration); ast_test_suite_event_notify("AOR_CONTACT_ADDED", @@ -893,6 +991,7 @@ static int unload_module(void) ast_manager_unregister(AMI_SHOW_REGISTRATIONS); ast_manager_unregister(AMI_SHOW_REGISTRATION_CONTACT_STATUSES); ast_sip_unregister_service(®istrar_module); + ast_sip_transport_monitor_unregister_all(register_contact_transport_shutdown_cb); return 0; } diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index cafbd52ec..b8ae8c185 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -241,15 +241,16 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me } ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->media.rtp.symmetric); + ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_ASYMMETRIC_CODEC, session->endpoint->asymmetric_rtp_codec); if (!session->endpoint->media.rtp.ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) { ice->stop(session_media->rtp); } - if (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) { + if (session->dtmf == AST_SIP_DTMF_RFC_4733 || session->dtmf == AST_SIP_DTMF_AUTO || session->dtmf == AST_SIP_DTMF_AUTO_INFO) { ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833); ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 1); - } else if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) { + } else if (session->dtmf == AST_SIP_DTMF_INBAND) { ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND); } @@ -269,7 +270,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me } static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs, - struct ast_sip_session_media *session_media) + struct ast_sip_session_media *session_media) { pjmedia_sdp_attr *attr; pjmedia_sdp_rtpmap *rtpmap; @@ -332,9 +333,19 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp } } } - if (!tel_event && (session->endpoint->dtmf == AST_SIP_DTMF_AUTO)) { + if (!tel_event && (session->dtmf == AST_SIP_DTMF_AUTO)) { ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND); } + + if (session->dtmf == AST_SIP_DTMF_AUTO_INFO) { + if (tel_event) { + ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833); + } else { + ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_NONE); + } + } + + /* Get the packetization, if it exists */ if ((attr = pjmedia_sdp_media_find_attr2(stream, "ptime", NULL))) { unsigned long framing = pj_strtoul(pj_strltrim(&attr->value)); @@ -401,7 +412,24 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel), AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_remove_by_type(caps, media_type); - ast_format_cap_append_from_cap(caps, joint, media_type); + + /* + * If we don't allow the sending codec to be changed on our side + * then get the best codec from the joint capabilities of the media + * type and use only that. This ensures the core won't start sending + * out a format that we aren't currently sending. + */ + if (!session->endpoint->asymmetric_rtp_codec) { + struct ast_format *best; + + best = ast_format_cap_get_best_by_type(joint, media_type); + if (best) { + ast_format_cap_append(caps, best, ast_format_cap_get_framing(joint)); + ao2_ref(best, -1); + } + } else { + ast_format_cap_append_from_cap(caps, joint, media_type); + } /* * Apply the new formats to the channel, potentially changing @@ -412,7 +440,8 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi ast_set_read_format(session->channel, ast_channel_readformat(session->channel)); ast_set_write_format(session->channel, ast_channel_writeformat(session->channel)); } - if ((session->endpoint->dtmf == AST_SIP_DTMF_AUTO) + + if ( ((session->dtmf == AST_SIP_DTMF_AUTO) || (session->dtmf == AST_SIP_DTMF_AUTO_INFO) ) && (ast_rtp_instance_dtmf_mode_get(session_media->rtp) == AST_RTP_DTMF_MODE_RFC2833) && (session->dsp)) { dsp_features = ast_dsp_get_features(session->dsp); @@ -1132,7 +1161,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as pj_str_t stmp; pjmedia_sdp_attr *attr; int index = 0; - int noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) ? AST_RTP_DTMF : 0; + int noncodec = (session->dtmf == AST_SIP_DTMF_RFC_4733 || session->dtmf == AST_SIP_DTMF_AUTO || session->dtmf == AST_SIP_DTMF_AUTO_INFO) ? AST_RTP_DTMF : 0; int min_packet_size = 0, max_packet_size = 0; int rtp_code; RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); @@ -1488,12 +1517,11 @@ static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struc ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID); /* Is the address within the SDP inside the same network? */ - if (transport_state->localnet - && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) { + if (ast_sip_transport_is_local(transport_state, &addr)) { return; } - ast_debug(5, "Setting media address to %s\n", transport->external_media_address); - pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address); + ast_debug(5, "Setting media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address)); + pj_strdup2(tdata->pool, &stream->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address)); } /*! \brief Function which stops the RTP instance */ diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 1fec089f8..ab6fce2c8 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -968,6 +968,46 @@ int ast_sip_session_refresh(struct ast_sip_session *session, return 0; } +int ast_sip_session_regenerate_answer(struct ast_sip_session *session, + ast_sip_session_sdp_creation_cb on_sdp_creation) +{ + pjsip_inv_session *inv_session = session->inv_session; + pjmedia_sdp_session *new_answer = NULL; + const pjmedia_sdp_session *previous_offer = NULL; + + /* The SDP answer can only be regenerated if it is still pending to be sent */ + if (!inv_session->neg || (pjmedia_sdp_neg_get_state(inv_session->neg) != PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER && + pjmedia_sdp_neg_get_state(inv_session->neg) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)) { + ast_log(LOG_WARNING, "Requested to regenerate local SDP answer for channel '%s' but negotiation in state '%s'\n", + ast_channel_name(session->channel), pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv_session->neg))); + return -1; + } + + pjmedia_sdp_neg_get_neg_remote(inv_session->neg, &previous_offer); + if (pjmedia_sdp_neg_get_state(inv_session->neg) == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { + /* Transition the SDP negotiator back to when it received the remote offer */ + pjmedia_sdp_neg_negotiate(inv_session->pool, inv_session->neg, 0); + pjmedia_sdp_neg_set_remote_offer(inv_session->pool, inv_session->neg, previous_offer); + } + + new_answer = create_local_sdp(inv_session, session, previous_offer); + if (!new_answer) { + ast_log(LOG_WARNING, "Could not create a new local SDP answer for channel '%s'\n", + ast_channel_name(session->channel)); + return -1; + } + + if (on_sdp_creation) { + if (on_sdp_creation(session, new_answer)) { + return -1; + } + } + + pjsip_inv_set_sdp_answer(inv_session, new_answer); + + return 0; +} + void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata) { handle_outgoing_response(session, tdata); @@ -1481,6 +1521,8 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, session->contact = ao2_bump(contact); session->inv_session = inv_session; + session->dtmf = endpoint->dtmf; + if (add_supplements(session)) { /* Release the ref held by session->inv_session */ ao2_ref(session, -1); @@ -1903,6 +1945,9 @@ int ast_sip_session_defer_termination(struct ast_sip_session *session) session->defer_terminate = 1; + session->defer_end = 1; + session->ended_while_deferred = 0; + session->scheduled_termination.id = 0; ao2_ref(session, +1); session->scheduled_termination.user_data = session; @@ -1940,6 +1985,7 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session) /* Already canceled or timer fired. */ return; } + session->defer_terminate = 0; if (session->terminate_while_deferred) { @@ -1951,6 +1997,22 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session) sip_session_defer_termination_stop_timer(session); } +void ast_sip_session_end_if_deferred(struct ast_sip_session *session) +{ + if (!session->defer_end) { + return; + } + + session->defer_end = 0; + + if (session->ended_while_deferred) { + /* Complete the session end started by the remote hangup. */ + ast_debug(3, "Ending session (%p) after being deferred\n", session); + session->ended_while_deferred = 0; + session_end(session); + } +} + struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg) { pjsip_inv_session *inv_session = pjsip_dlg_get_inv_session(dlg); @@ -2532,6 +2594,11 @@ static int session_end(void *vsession) iter->session_end(session); } } + + /* Release any media resources. */ + ao2_cleanup(session->media); + session->media = NULL; + return 0; } @@ -2636,6 +2703,12 @@ static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) } if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + if (session->defer_end) { + ast_debug(3, "Deferring session (%p) end\n", session); + session->ended_while_deferred = 1; + return; + } + if (ast_sip_push_task(session->serializer, session_end, session)) { /* Do it anyway even though this is not the right thread. */ session_end(session); @@ -3122,11 +3195,9 @@ static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_trans ast_copy_pj_str(host, &sdp->conn->addr, sizeof(host)); ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID); - if (!transport_state->localnet - || (transport_state->localnet - && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW)) { - ast_debug(5, "Setting external media address to %s\n", transport->external_media_address); - pj_strdup2(tdata->pool, &sdp->conn->addr, transport->external_media_address); + if (ast_sip_transport_is_nonlocal(transport_state, &addr)) { + ast_debug(5, "Setting external media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address)); + pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address)); } } diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in index a39485e66..5bc0bf40e 100644 --- a/res/res_pjsip_session.exports.in +++ b/res/res_pjsip_session.exports.in @@ -3,6 +3,7 @@ LINKER_SYMBOL_PREFIXast_sip_session_terminate; LINKER_SYMBOL_PREFIXast_sip_session_defer_termination; LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel; + LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred; LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler; LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler; LINKER_SYMBOL_PREFIXast_sip_session_register_supplement; @@ -13,6 +14,7 @@ LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore; LINKER_SYMBOL_PREFIXast_sip_session_get_identity; LINKER_SYMBOL_PREFIXast_sip_session_refresh; + LINKER_SYMBOL_PREFIXast_sip_session_regenerate_answer; LINKER_SYMBOL_PREFIXast_sip_session_send_response; LINKER_SYMBOL_PREFIXast_sip_session_send_request; LINKER_SYMBOL_PREFIXast_sip_session_create_invite; diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c index 0ce6474e3..58da6a080 100644 --- a/res/res_pjsip_t38.c +++ b/res/res_pjsip_t38.c @@ -278,7 +278,7 @@ static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_sess /* Move the image media stream to the front and have it as the only stream, pjmedia will fill in * dummy streams for the rest */ - for (stream = 0; stream < sdp->media_count++; ++stream) { + for (stream = 0; stream < sdp->media_count; ++stream) { if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) { sdp->media[0] = sdp->media[stream]; sdp->media_count = 1; @@ -294,21 +294,22 @@ static int t38_reinvite_response_cb(struct ast_sip_session *session, pjsip_rx_da { struct pjsip_status_line status = rdata->msg_info.msg->line.status; struct t38_state *state; - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); + struct ast_sip_session_media *session_media = NULL; if (status.code == 100) { return 0; } - if (!(state = t38_state_get_or_alloc(session)) || + if (!session->channel || !(state = t38_state_get_or_alloc(session)) || !(session_media = ao2_find(session->media, "image", OBJ_KEY))) { ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n", - ast_channel_name(session->channel)); + session->channel ? ast_channel_name(session->channel) : "unknown channel"); return 0; } t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED); + ao2_cleanup(session_media); return 0; } @@ -403,16 +404,21 @@ static int t38_interpret_parameters(void *obj) static struct ast_frame *t38_framehook_write(struct ast_channel *chan, struct ast_sip_session *session, struct ast_frame *f) { - if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS && - session->endpoint->media.t38.enabled) { - struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f); + if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS) { + if (session->endpoint->media.t38.enabled) { + struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f); - if (!data) { - return f; - } + if (!data) { + return f; + } - if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) { - ao2_ref(data, -1); + if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) { + ao2_ref(data, -1); + } + } else { + struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REFUSED, }; + ast_debug(2, "T.38 support not enabled, rejecting T.38 control packet\n"); + ast_queue_control_data(session->channel, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); } } else if (f->frametype == AST_FRAME_MODEM) { struct ast_sip_session_media *session_media; @@ -503,10 +509,7 @@ static void t38_attach_framehook(struct ast_sip_session *session) return; } - /* Only attach the framehook if t38 is enabled for the endpoint */ - if (!session->endpoint->media.t38.enabled) { - return; - } + /* Always attach the framehook so we can quickly reject */ ast_channel_lock(session->channel); @@ -879,12 +882,11 @@ static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struc ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID); /* Is the address within the SDP inside the same network? */ - if (transport_state->localnet - && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) { + if (ast_sip_transport_is_local(transport_state, &addr)) { return; } - ast_debug(5, "Setting media address to %s\n", transport->external_media_address); - pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address); + ast_debug(5, "Setting media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address)); + pj_strdup2(tdata->pool, &stream->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address)); } /*! \brief Function which destroys the UDPTL instance when session ends */ diff --git a/res/res_pjsip_transport_management.c b/res/res_pjsip_transport_management.c index 3e129dc5f..eb92eb7a5 100644 --- a/res/res_pjsip_transport_management.c +++ b/res/res_pjsip_transport_management.c @@ -33,8 +33,8 @@ #include "asterisk/module.h" #include "asterisk/astobj2.h" -/*! \brief Number of buckets for keepalive transports */ -#define TRANSPORTS_BUCKETS 53 +/*! \brief Number of buckets for monitored transports */ +#define TRANSPORTS_BUCKETS 127 #define IDLE_TIMEOUT (pjsip_cfg()->tsx.td) @@ -53,9 +53,6 @@ static pthread_t keepalive_thread = AST_PTHREADT_NULL; /*! \brief The global interval at which to send keepalives */ static unsigned int keepalive_interval; -/*! \brief Existing transport manager callback that we need to invoke */ -static pjsip_tp_state_callback tpmgr_state_callback; - /*! \brief Structure for transport to be monitored */ struct monitored_transport { /*! \brief The underlying PJSIP transport */ @@ -114,7 +111,7 @@ AST_THREADSTORAGE(desc_storage); static int idle_sched_cb(const void *data) { - struct monitored_transport *keepalive = (struct monitored_transport *) data; + struct monitored_transport *monitored = (struct monitored_transport *) data; if (!pj_thread_is_registered()) { pj_thread_t *thread; @@ -123,7 +120,7 @@ static int idle_sched_cb(const void *data) desc = ast_threadstorage_get(&desc_storage, sizeof(pj_thread_desc)); if (!desc) { ast_log(LOG_ERROR, "Could not get thread desc from thread-local storage.\n"); - ao2_ref(keepalive, -1); + ao2_ref(monitored, -1); return 0; } @@ -132,22 +129,22 @@ static int idle_sched_cb(const void *data) pj_thread_register("Transport Monitor", *desc, &thread); } - if (!keepalive->sip_received) { + if (!monitored->sip_received) { ast_log(LOG_NOTICE, "Shutting down transport '%s' since no request was received in %d seconds\n", - keepalive->transport->info, IDLE_TIMEOUT / 1000); - pjsip_transport_shutdown(keepalive->transport); + monitored->transport->info, IDLE_TIMEOUT / 1000); + pjsip_transport_shutdown(monitored->transport); } - ao2_ref(keepalive, -1); + ao2_ref(monitored, -1); return 0; } /*! \brief Destructor for keepalive transport */ static void monitored_transport_destroy(void *obj) { - struct monitored_transport *keepalive = obj; + struct monitored_transport *monitored = obj; - pjsip_transport_dec_ref(keepalive->transport); + pjsip_transport_dec_ref(monitored->transport); } /*! \brief Callback invoked when transport changes occur */ @@ -178,14 +175,13 @@ static void monitored_transport_state_callback(pjsip_transport *transport, pjsip /* 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); + } else { + /* monitored ref successfully passed to idle_sched_cb() */ + break; } - } else { - /* No scheduled task, so get rid of the allocation reference */ - ao2_ref(monitored, -1); } + ao2_ref(monitored, -1); break; case PJSIP_TP_STATE_SHUTDOWN: case PJSIP_TP_STATE_DISCONNECTED: @@ -197,13 +193,12 @@ static void monitored_transport_state_callback(pjsip_transport *transport, pjsip ao2_ref(transports, -1); } - - /* Forward to the old state callback if present */ - if (tpmgr_state_callback) { - tpmgr_state_callback(transport, state, info); - } } +struct ast_sip_tpmgr_state_callback monitored_transport_reg = { + monitored_transport_state_callback, +}; + /*! \brief Hashing function for monitored transport */ static int monitored_transport_hash_fn(const void *obj, int flags) { @@ -327,16 +322,9 @@ 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) { @@ -363,8 +351,7 @@ static int load_module(void) ast_sip_register_service(&idle_monitor_module); - tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr); - pjsip_tpmgr_set_state_cb(tpmgr, &monitored_transport_state_callback); + ast_sip_transport_state_register(&monitored_transport_reg); ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &keepalive_global_observer); ast_sorcery_reload_object(ast_sip_get_sorcery(), "global"); @@ -375,8 +362,6 @@ static int load_module(void) static int unload_module(void) { - pjsip_tpmgr *tpmgr; - if (keepalive_interval) { keepalive_interval = 0; if (keepalive_thread != AST_PTHREADT_NULL) { @@ -388,10 +373,7 @@ static int unload_module(void) 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_transport_state_unregister(&monitored_transport_reg); ast_sip_unregister_service(&idle_monitor_module); diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c index 1b9d616de..c04594fee 100644 --- a/res/res_pjsip_transport_websocket.c +++ b/res/res_pjsip_transport_websocket.c @@ -145,6 +145,7 @@ static int transport_create(void *data) { struct transport_create_data *create_data = data; struct ws_transport *newtransport = NULL; + pjsip_tp_state_callback state_cb; pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); struct pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(endpt); @@ -161,6 +162,10 @@ static int transport_create(void *data) goto on_error; } + /* Give websocket transport a unique name for its lifetime */ + snprintf(newtransport->transport.obj_name, PJ_MAX_OBJ_NAME, "ws%p", + &newtransport->transport); + newtransport->transport.endpt = endpt; if (!(pool = pjsip_endpt_create_pool(endpt, "ws", 512, 512))) { @@ -219,6 +224,7 @@ static int transport_create(void *data) newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type); newtransport->transport.info = (char *)pj_pool_alloc(newtransport->transport.pool, 64); + newtransport->transport.dir = PJSIP_TP_DIR_INCOMING; newtransport->transport.tpmgr = tpmgr; newtransport->transport.send_msg = &ws_send_msg; newtransport->transport.destroy = &ws_destroy; @@ -242,6 +248,16 @@ static int transport_create(void *data) } create_data->transport = newtransport; + + /* Notify application of transport state */ + state_cb = pjsip_tpmgr_get_state_cb(newtransport->transport.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + memset(&state_info, 0, sizeof(state_info)); + state_cb(&newtransport->transport, PJSIP_TP_STATE_CONNECTED, &state_info); + } + return 0; on_error: @@ -302,10 +318,14 @@ static int get_write_timeout(void) for (; (transport_state = ao2_iterator_next(&it_transport_states)); ao2_cleanup(transport_state)) { struct ast_sip_transport *transport; + if (transport_state->type != AST_TRANSPORT_WS && transport_state->type != AST_TRANSPORT_WSS) { continue; } transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id); + if (!transport) { + continue; + } ast_debug(5, "Found %s transport with write timeout: %d\n", transport->type == AST_TRANSPORT_WS ? "WS" : "WSS", transport->write_timeout); @@ -361,6 +381,7 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par if (ast_sip_push_task_synchronous(serializer, transport_create, &create_data)) { ast_log(LOG_ERROR, "Could not create WebSocket transport.\n"); + ast_taskprocessor_unreference(serializer); ast_websocket_unref(session); return; } diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c index 298235cbc..0977159ee 100644 --- a/res/res_pjsip_xpidf_body_generator.c +++ b/res/res_pjsip_xpidf_body_generator.c @@ -63,7 +63,7 @@ static int xpidf_generate_body_content(void *body, void *data) pj_xml_node *msnsubstatus; ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, - &pidfstate, &pidfnote, &local_state); + &pidfstate, &pidfnote, &local_state, 0); ast_sip_presence_xml_find_node_attr(state_data->pool, pres, "atom", "id", &atom, &attr); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index f75482f7f..77027aafe 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -218,8 +218,9 @@ static AST_RWLIST_HEAD_STATIC(host_candidates, ast_ice_host_candidate); /*! \brief RTP learning mode tracking information */ struct rtp_learning_info { - int max_seq; /*!< The highest sequence number received */ - int packets; /*!< The number of remaining packets before the source is accepted */ + int max_seq; /*!< The highest sequence number received */ + int packets; /*!< The number of remaining packets before the source is accepted */ + struct timeval received; /*!< The time of the last received packet */ }; #ifdef HAVE_OPENSSL_SRTP @@ -302,6 +303,7 @@ struct ast_rtp { void *data; struct ast_rtcp *rtcp; struct ast_rtp *bridged; /*!< Who we are Packet bridged to */ + unsigned int asymmetric_codec; /*!< Indicate if asymmetric send/receive codecs are allowed */ enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */ struct ast_sockaddr strict_rtp_address; /*!< Remote address information for strict RTP purposes */ @@ -311,7 +313,6 @@ struct ast_rtp { * but these are in place to keep learning mode sequence values sealed from their normal counterparts. */ struct rtp_learning_info rtp_source_learn; /* Learning mode track for the expected RTP source */ - struct rtp_learning_info alt_source_learn; /* Learning mode tracking for a new RTP source after one has been chosen */ struct rtp_red *red; @@ -319,6 +320,7 @@ struct ast_rtp { ast_cond_t cond; /*!< ICE/TURN condition for signaling */ struct ice_wrap *ice; /*!< ao2 wrapped ICE session */ + enum ast_rtp_ice_role role; /*!< Our role in ICE negotiation */ pj_turn_sock *turn_rtp; /*!< RTP TURN relay */ pj_turn_sock *turn_rtcp; /*!< RTCP TURN relay */ pj_turn_state_t turn_state; /*!< Current state of the TURN relay session */ @@ -677,11 +679,41 @@ static void ice_wrap_dtor(void *vdoomed) } } +static void ast2pj_rtp_ice_role(enum ast_rtp_ice_role ast_role, enum pj_ice_sess_role *pj_role) +{ + switch (ast_role) { + case AST_RTP_ICE_ROLE_CONTROLLED: + *pj_role = PJ_ICE_SESS_ROLE_CONTROLLED; + break; + case AST_RTP_ICE_ROLE_CONTROLLING: + *pj_role = PJ_ICE_SESS_ROLE_CONTROLLING; + break; + } +} + +static void pj2ast_rtp_ice_role(enum pj_ice_sess_role pj_role, enum ast_rtp_ice_role *ast_role) +{ + switch (pj_role) { + case PJ_ICE_SESS_ROLE_CONTROLLED: + *ast_role = AST_RTP_ICE_ROLE_CONTROLLED; + return; + case PJ_ICE_SESS_ROLE_CONTROLLING: + *ast_role = AST_RTP_ICE_ROLE_CONTROLLING; + return; + case PJ_ICE_SESS_ROLE_UNKNOWN: + /* Don't change anything */ + return; + default: + /* If we aren't explicitly handling something, it's a bug */ + ast_assert(0); + return; + } +} + /*! \pre instance is locked */ static int ice_reset_session(struct ast_rtp_instance *instance) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); - pj_ice_sess_role role = rtp->ice->real_ice->role; int res; ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance); @@ -693,7 +725,9 @@ static int ice_reset_session(struct ast_rtp_instance *instance) ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance); res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1); if (!res) { - /* Preserve the role that the old ICE session used */ + /* Use the current expected role for the ICE session */ + enum pj_ice_sess_role role = PJ_ICE_SESS_ROLE_UNKNOWN; + ast2pj_rtp_ice_role(rtp->role, &role); pj_ice_sess_change_role(rtp->ice->real_ice, role); } @@ -765,6 +799,8 @@ static void ast_rtp_ice_start(struct ast_rtp_instance *instance) ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance); ao2_cleanup(rtp->ice_proposed_remote_candidates); rtp->ice_proposed_remote_candidates = NULL; + /* If this ICE session is being preserved then go back to the role it currently is */ + pj2ast_rtp_ice_role(rtp->ice->real_ice->role, &rtp->role); return; } @@ -938,10 +974,7 @@ static void ast_rtp_ice_set_role(struct ast_rtp_instance *instance, enum ast_rtp return; } - pj_thread_register_check(); - - pj_ice_sess_change_role(rtp->ice->real_ice, role == AST_RTP_ICE_ROLE_CONTROLLED ? - PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING); + rtp->role = role; } /*! \pre instance is locked */ @@ -1291,6 +1324,8 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast pj_turn_session_info info; struct ast_sockaddr local, loop; pj_status_t status; + pj_turn_sock_cfg turn_sock_cfg; + struct ice_wrap *ice; ast_rtp_instance_get_local_address(instance, &local); if (ast_sockaddr_is_ipv4(&local)) { @@ -1353,11 +1388,20 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast pj_stun_config_init(&stun_config, &cachingpool.factory, 0, rtp->ioqueue->ioqueue, rtp->ioqueue->timerheap); + /* Use ICE session group lock for TURN session to avoid deadlock */ + pj_turn_sock_cfg_default(&turn_sock_cfg); + ice = rtp->ice; + if (ice) { + turn_sock_cfg.grp_lock = ice->real_ice->grp_lock; + ao2_ref(ice, +1); + } + /* Release the instance lock to avoid deadlock with PJPROJECT group lock */ ao2_unlock(instance); status = pj_turn_sock_create(&stun_config, ast_sockaddr_is_ipv4(&addr) ? pj_AF_INET() : pj_AF_INET6(), conn_type, - turn_cb, NULL, instance, turn_sock); + turn_cb, &turn_sock_cfg, instance, turn_sock); + ao2_cleanup(ice); if (status != PJ_SUCCESS) { ast_log(LOG_WARNING, "Could not create a TURN client socket\n"); ao2_lock(instance); @@ -2522,6 +2566,17 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s return -1; } if (!rtp->passthrough) { + /* If a unidirectional ICE negotiation occurs then lock on to the source of the + * ICE traffic and use it as the target. This will occur if the remote side only + * wants to receive media but never send to us. + */ + if (!rtp->ice_active_remote_candidates && !rtp->ice_proposed_remote_candidates) { + if (rtcp) { + ast_sockaddr_copy(&rtp->rtcp->them, sa); + } else { + ast_rtp_instance_set_remote_address(instance, sa); + } + } return 0; } rtp->passthrough = 0; @@ -2565,17 +2620,22 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz #ifdef HAVE_PJPROJECT if (rtp->ice) { + enum ast_rtp_ice_component_type component = rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP; pj_status_t status; struct ice_wrap *ice; + /* If RTCP is sharing the same socket then use the same component */ + if (rtcp && rtp->rtcp->s == rtp->s) { + component = AST_RTP_ICE_COMPONENT_RTP; + } + pj_thread_register_check(); /* Release the instance lock to avoid deadlock with PJPROJECT group lock */ ice = rtp->ice; ao2_ref(ice, +1); ao2_unlock(instance); - status = pj_ice_sess_send_data(ice->real_ice, - rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP, temp, len); + status = pj_ice_sess_send_data(ice->real_ice, component, temp, len); ao2_ref(ice, -1); ao2_lock(instance); if (status == PJ_SUCCESS) { @@ -2695,6 +2755,7 @@ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq) { info->max_seq = seq - 1; info->packets = learning_min_sequential; + memset(&info->received, 0, sizeof(info->received)); } /*! @@ -2709,6 +2770,13 @@ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq) */ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t seq) { + if (!ast_tvzero(info->received) && ast_tvdiff_ms(ast_tvnow(), info->received) < 5) { + /* During the probation period the minimum amount of media we'll accept is + * 10ms so give a reasonable 5ms buffer just in case we get it sporadically. + */ + return 1; + } + if (seq == info->max_seq + 1) { /* packet is in sequence */ info->packets--; @@ -2717,6 +2785,7 @@ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t info->packets = learning_min_sequential - 1; } info->max_seq = seq; + info->received = ast_tvnow(); return (info->packets == 0); } @@ -2991,10 +3060,9 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, /* Set default parameters on the newly created RTP structure */ rtp->ssrc = ast_random(); rtp->seqno = ast_random() & 0x7fff; - rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN); + rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_CLOSED : STRICT_RTP_OPEN); if (strictrtp) { rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno); - rtp_learning_seq_init(&rtp->alt_source_learn, (uint16_t)rtp->seqno); } /* Create a new socket for us to listen on and use */ @@ -3443,7 +3511,7 @@ static void ast_rtp_change_source(struct ast_rtp_instance *instance) ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc); res_srtp->change_source(srtp, rtp->ssrc, ssrc); if (rtcp_srtp != srtp) { - res_srtp->change_source(srtp, rtp->ssrc, ssrc); + res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc); } } @@ -4535,17 +4603,6 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c packetwords = size / 4; - if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { - /* Send to whoever sent to us */ - if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) { - ast_sockaddr_copy(&rtp->rtcp->them, addr); - if (rtpdebug) { - ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); - } - } - } - ast_debug(1, "Got RTCP report of %zu bytes\n", size); while (position < packetwords) { @@ -4574,6 +4631,25 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c return &ast_null_frame; } + if ((rtp->strict_rtp_state != STRICT_RTP_OPEN) && (rtcp_report->ssrc != rtp->themssrc)) { + /* Skip over this RTCP record as it does not contain the correct SSRC */ + position += (length + 1); + ast_debug(1, "%p -- Received RTCP report from %s, dropping due to strict RTP protection. Received SSRC '%u' but expected '%u'\n", + rtp, ast_sockaddr_stringify(addr), rtcp_report->ssrc, rtp->themssrc); + continue; + } + + if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { + /* Send to whoever sent to us */ + if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) { + ast_sockaddr_copy(&rtp->rtcp->them, addr); + if (rtpdebug) { + ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n", + ast_sockaddr_stringify(&rtp->rtcp->them)); + } + } + } + if (rtcp_debug_test_addr(addr)) { ast_verbose("\n\nGot RTCP from %s\n", ast_sockaddr_stringify(addr)); @@ -4795,9 +4871,6 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance, return -1; } - rtp->rxcount++; - rtp->rxoctetcount += (len - hdrlen); - /* If the payload coming in is not one of the negotiated ones then send it to the core, this will cause formats to change and the bridge to break */ if (ast_rtp_codecs_find_payload_code(ast_rtp_instance_get_codecs(instance1), bridged_payload) == -1) { ast_debug(1, "Unsupported payload type received \n"); @@ -4820,6 +4893,23 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance, return -1; } + + ao2_replace(rtp->lastrxformat, payload_type->format); + ao2_replace(bridged->lasttxformat, payload_type->format); + + /* + * If bridged peer has already received rtp, perform the asymmetric codec check + * if that feature has been activated + */ + if (!bridged->asymmetric_codec && bridged->lastrxformat != ast_format_none) { + if (ast_format_cmp(bridged->lasttxformat, bridged->lastrxformat) == AST_FORMAT_CMP_NOT_EQUAL) { + ast_debug(1, "Asymmetric RTP codecs detected (TX: %s, RX: %s) sending frame to core\n", + ast_format_get_name(bridged->lasttxformat), + ast_format_get_name(bridged->lastrxformat)); + return -1; + } + } + /* If the marker bit has been explicitly set turn it on */ if (ast_test_flag(rtp, FLAG_NEED_MARKER_BIT)) { mark = 1; @@ -4980,39 +5070,37 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc return &ast_null_frame; } + /* If the version is not what we expected by this point then just drop the packet */ + if (version != 2) { + return &ast_null_frame; + } + /* If strict RTP protection is enabled see if we need to learn the remote address or if we need to drop the packet */ if (rtp->strict_rtp_state == STRICT_RTP_LEARN) { - ast_debug(1, "%p -- Probation learning mode pass with source address %s\n", rtp, ast_sockaddr_stringify(&addr)); - /* For now, we always copy the address. */ - ast_sockaddr_copy(&rtp->strict_rtp_address, &addr); - - /* Send the rtp and the seqno from header to rtp_learning_rtp_seq_update to see whether we can exit or not*/ - if (rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) { - ast_debug(1, "%p -- Probation at seq %d with %d to go; discarding frame\n", - rtp, rtp->rtp_source_learn.max_seq, rtp->rtp_source_learn.packets); - return &ast_null_frame; - } - - ast_verb(4, "%p -- Probation passed - setting RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr)); - rtp->strict_rtp_state = STRICT_RTP_CLOSED; - } - if (rtp->strict_rtp_state == STRICT_RTP_CLOSED) { if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { - /* Always reset the alternate learning source */ - rtp_learning_seq_init(&rtp->alt_source_learn, seqno); + /* We are learning a new address but have received traffic from the existing address, + * accept it but reset the current learning for the new source so it only takes over + * once sufficient traffic has been received. */ + rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); } else { /* Start trying to learn from the new address. If we pass a probationary period with * it, that means we've stopped getting RTP from the original source and we should * switch to it. */ - if (rtp_learning_rtp_seq_update(&rtp->alt_source_learn, seqno)) { + if (rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) { ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets\n", - rtp, ast_sockaddr_stringify(&addr), rtp->alt_source_learn.packets); + rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets); return &ast_null_frame; } - ast_verb(4, "%p -- Switching RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr)); ast_sockaddr_copy(&rtp->strict_rtp_address, &addr); + + ast_verb(4, "%p -- Probation passed - setting RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr)); + rtp->strict_rtp_state = STRICT_RTP_CLOSED; } + } else if (rtp->strict_rtp_state == STRICT_RTP_CLOSED && ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { + ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection.\n", + rtp, ast_sockaddr_stringify(&addr)); + return &ast_null_frame; } /* If symmetric RTP is enabled see if the remote side is not what we expected and change where we are sending audio */ @@ -5033,18 +5121,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc } } - /* If we are directly bridged to another instance send the audio directly out */ - instance1 = ast_rtp_instance_get_bridged(instance); - if (instance1 - && !bridge_p2p_rtp_write(instance, instance1, rtpheader, res, hdrlen)) { - return &ast_null_frame; - } - - /* If the version is not what we expected by this point then just drop the packet */ - if (version != 2) { - return &ast_null_frame; - } - /* Pull out the various other fields we will need */ payloadtype = (seqno & 0x7f0000) >> 16; padding = seqno & (1 << 29); @@ -5144,6 +5220,28 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc rtp->themssrc = ntohl(rtpheader[2]); /* Record their SSRC to put in future RR */ } + + /* If we are directly bridged to another instance send the audio directly out, + * but only after updating core information about the received traffic so that + * outgoing RTCP reflects it. + */ + instance1 = ast_rtp_instance_get_bridged(instance); + if (instance1 + && !bridge_p2p_rtp_write(instance, instance1, rtpheader, res, hdrlen)) { + struct timeval rxtime; + struct ast_frame *f; + + /* Update statistics for jitter so they are correct in RTCP */ + calc_rxstamp(&rxtime, rtp, timestamp, mark); + + /* When doing P2P we don't need to raise any frames about SSRC change to the core */ + while ((f = AST_LIST_REMOVE_HEAD(&frames, frame_list)) != NULL) { + ast_frfree(f); + } + + return &ast_null_frame; + } + if (rtp_debug_test_addr(&addr)) { ast_verbose("Got RTP packet from %s (type %-2.2d, seq %-6.6u, ts %-6.6u, len %-6.6d)\n", ast_sockaddr_stringify(&addr), @@ -5472,6 +5570,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro rtp->rtcp = NULL; } } + } else if (property == AST_RTP_PROPERTY_ASYMMETRIC_CODEC) { + rtp->asymmetric_codec = value; } } @@ -5518,7 +5618,11 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct rtp->rxseqno = 0; - if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN) { + if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN && !ast_sockaddr_isnull(addr) && + ast_sockaddr_cmp(addr, &rtp->strict_rtp_address)) { + /* We only need to learn a new strict source address if we've been told the source is + * changing to something different. + */ rtp->strict_rtp_state = STRICT_RTP_LEARN; rtp_learning_seq_init(&rtp->rtp_source_learn, rtp->seqno); } diff --git a/res/res_smdi.c b/res/res_smdi.c index f4804c7cb..9a40227f2 100644 --- a/res/res_smdi.c +++ b/res/res_smdi.c @@ -610,13 +610,12 @@ static void *smdi_read(void *iface_p) ast_debug(1, "Read a 'D' ... it's an MD message.\n"); - if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) { + md_msg = ao2_alloc(sizeof(*md_msg), NULL); + if (!md_msg) { ao2_ref(iface, -1); return NULL; } - md_msg = ao2_alloc(sizeof(*md_msg), NULL); - /* read the message desk number */ for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) { md_msg->mesg_desk_num[i] = fgetc(iface->file); @@ -712,13 +711,12 @@ static void *smdi_read(void *iface_p) ast_debug(1, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n"); - if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) { + mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL); + if (!mwi_msg) { ao2_ref(iface, -1); return NULL; } - mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL); - /* discard the 'I' (from 'MWI') */ fgetc(iface->file); diff --git a/res/res_stasis.c b/res/res_stasis.c index 9d7bc4c24..b1cea3ad5 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -1057,8 +1057,18 @@ static void channel_stolen_cb(void *data, struct ast_channel *old_chan, struct a { struct stasis_app_control *control; - /* find control */ - control = ao2_callback(app_controls, 0, masq_match_cb, old_chan); + /* + * At this point, old_chan is the channel pointer that is in Stasis() and + * has the unknown channel's name in it while new_chan is the channel pointer + * that is not in Stasis(), but has the guts of the channel that Stasis() knows + * about. + * + * Find and unlink control since the channel has a new name/uniqueid + * and its hash has changed. Since the channel is leaving stasis don't + * bother putting it back into the container. Nobody is going to + * remove it from the container later. + */ + control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, old_chan); if (!control) { ast_log(LOG_ERROR, "Could not find control for masqueraded channel\n"); return; @@ -1099,8 +1109,10 @@ static void channel_replaced_cb(void *data, struct ast_channel *old_chan, struct return; } - /* find, unlink, and relink control since the channel has a new name and - * its hash has likely changed */ + /* + * Find, unlink, and relink control since the channel has a new + * name/uniqueid and its hash has changed. + */ control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, new_chan); if (!control) { ast_log(LOG_ERROR, "Could not find control for masquerading channel\n"); diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c index 29e75660c..51101dd7b 100644 --- a/res/res_stasis_device_state.c +++ b/res/res_stasis_device_state.c @@ -108,7 +108,6 @@ static int device_state_subscriptions_cmp(void *obj, void *arg, int flags) static void device_state_subscription_destroy(void *obj) { struct device_state_subscription *sub = obj; - sub->sub = stasis_unsubscribe_and_join(sub->sub); ast_string_field_free_memory(sub); } @@ -154,6 +153,9 @@ static struct device_state_subscription *find_device_state_subscription( static void remove_device_state_subscription( struct device_state_subscription *sub) { + if (sub->sub) { + sub->sub = stasis_unsubscribe_and_join(sub->sub); + } ao2_unlink_flags(device_state_subscriptions, sub, OBJ_NOLOCK); } diff --git a/res/res_stasis_snoop.c b/res/res_stasis_snoop.c index abdef6e46..da66894f6 100644 --- a/res/res_stasis_snoop.c +++ b/res/res_stasis_snoop.c @@ -74,6 +74,8 @@ struct stasis_app_snoop { unsigned int whisper_active:1; /*! \brief Uniqueid of the channel this snoop is snooping on */ char uniqueid[AST_MAX_UNIQUEID]; + /*! \brief A frame of silence to use when the audiohook returns null */ + struct ast_frame silence; }; /*! \brief Destructor for snoop structure */ @@ -93,6 +95,11 @@ static void snoop_destroy(void *obj) ast_audiohook_destroy(&snoop->whisper); } + if (snoop->silence.data.ptr) { + ast_free(snoop->silence.data.ptr); + snoop->silence.data.ptr = NULL; + } + ast_free(snoop->app); ast_channel_cleanup(snoop->chan); @@ -199,7 +206,7 @@ static struct ast_frame *snoop_read(struct ast_channel *chan) frame = ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples, snoop->spy_direction, snoop->spy_format); ast_audiohook_unlock(&snoop->spy); - return frame ? frame : &ast_null_frame; + return frame ? frame : &snoop->silence; } /*! \brief Callback function for hanging up a Snoop channel */ @@ -385,6 +392,19 @@ struct ast_channel *stasis_app_control_snoop(struct ast_channel *chan, snoop->spy_samples = ast_format_get_sample_rate(snoop->spy_format) / (1000 / SNOOP_INTERVAL); snoop->spy_active = 1; + + snoop->silence.frametype = AST_FRAME_VOICE, + snoop->silence.datalen = snoop->spy_samples * sizeof(uint16_t), + snoop->silence.samples = snoop->spy_samples, + snoop->silence.mallocd = 0, + snoop->silence.offset = 0, + snoop->silence.src = __PRETTY_FUNCTION__, + snoop->silence.subclass.format = snoop->spy_format, + snoop->silence.data.ptr = ast_calloc(snoop->spy_samples, sizeof(uint16_t)); + if (!snoop->silence.data.ptr) { + ast_hangup(snoop->chan); + return NULL; + } } /* If whispering is enabled set up the audiohook */ diff --git a/res/res_xmpp.c b/res/res_xmpp.c index 95d3cc009..d8487f5ba 100644 --- a/res/res_xmpp.c +++ b/res/res_xmpp.c @@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/manager.h" #include "asterisk/cli.h" #include "asterisk/config_options.h" +#include "asterisk/json.h" /*** DOCUMENTATION <application name="JabberSend" language="en_US" module="res_xmpp"> @@ -323,6 +324,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <configOption name="secret"> <synopsis>XMPP password</synopsis> </configOption> + <configOption name="refresh_token"> + <synopsis>Google OAuth 2.0 refresh token</synopsis> + </configOption> + <configOption name="oauth_clientid"> + <synopsis>Google OAuth 2.0 application's client id</synopsis> + </configOption> + <configOption name="oauth_secret"> + <synopsis>Google OAuth 2.0 application's secret</synopsis> + </configOption> <configOption name="serverhost"> <synopsis>Route to server, e.g. talk.google.com</synopsis> </configOption> @@ -461,6 +471,9 @@ struct ast_xmpp_client_config { AST_STRING_FIELD(name); /*!< Name of the client connection */ AST_STRING_FIELD(user); /*!< Username to use for authentication */ AST_STRING_FIELD(password); /*!< Password to use for authentication */ + AST_STRING_FIELD(refresh_token); /*!< Refresh token to use for OAuth authentication */ + AST_STRING_FIELD(oauth_clientid); /*!< Client ID to use for OAuth authentication */ + AST_STRING_FIELD(oauth_secret); /*!< Secret to use for OAuth authentication */ AST_STRING_FIELD(server); /*!< Server hostname */ AST_STRING_FIELD(statusmsg); /*!< Status message for presence */ AST_STRING_FIELD(pubsubnode); /*!< Pubsub node */ @@ -529,6 +542,7 @@ static ast_cond_t message_received_condition; static ast_mutex_t messagelock; static int xmpp_client_config_post_apply(void *obj, void *arg, int flags); +static int fetch_access_token(struct ast_xmpp_client_config *cfg); /*! \brief Destructor function for configuration */ static void ast_xmpp_client_config_destructor(void *obj) @@ -761,12 +775,16 @@ static int xmpp_config_prelink(void *newitem) if (ast_strlen_zero(clientcfg->user)) { ast_log(LOG_ERROR, "No user specified on client '%s'\n", clientcfg->name); return -1; - } else if (ast_strlen_zero(clientcfg->password)) { - ast_log(LOG_ERROR, "No password specified on client '%s'\n", clientcfg->name); + } else if (ast_strlen_zero(clientcfg->password) && ast_strlen_zero(clientcfg->refresh_token)) { + ast_log(LOG_ERROR, "No password or refresh_token specified on client '%s'\n", clientcfg->name); return -1; } else if (ast_strlen_zero(clientcfg->server)) { ast_log(LOG_ERROR, "No server specified on client '%s'\n", clientcfg->name); return -1; + } else if (!ast_strlen_zero(clientcfg->refresh_token) && + (ast_strlen_zero(clientcfg->oauth_clientid) || ast_strlen_zero(clientcfg->oauth_secret))) { + ast_log(LOG_ERROR, "No oauth_clientid or oauth_secret specified, so client '%s' can't be used\n", clientcfg->name); + return -1; } /* If this is a new connection force a reconnect */ @@ -778,6 +796,9 @@ static int xmpp_config_prelink(void *newitem) /* If any configuration options are changing that would require reconnecting set the bit so we will do so if possible */ if (strcmp(clientcfg->user, oldclientcfg->user) || strcmp(clientcfg->password, oldclientcfg->password) || + strcmp(clientcfg->refresh_token, oldclientcfg->refresh_token) || + strcmp(clientcfg->oauth_clientid, oldclientcfg->oauth_clientid) || + strcmp(clientcfg->oauth_secret, oldclientcfg->oauth_secret) || strcmp(clientcfg->server, oldclientcfg->server) || (clientcfg->port != oldclientcfg->port) || (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) != ast_test_flag(&oldclientcfg->flags, XMPP_COMPONENT)) || @@ -2786,7 +2807,13 @@ static int xmpp_client_authenticate_sasl(struct ast_xmpp_client *client, struct } iks_insert_attrib(auth, "xmlns", IKS_NS_XMPP_SASL); - iks_insert_attrib(auth, "mechanism", "PLAIN"); + if (!ast_strlen_zero(cfg->refresh_token)) { + iks_insert_attrib(auth, "mechanism", "X-OAUTH2"); + iks_insert_attrib(auth, "auth:service", "oauth2"); + iks_insert_attrib(auth, "xmlns:auth", "http://www.google.com/talk/protocol/auth"); + } else { + iks_insert_attrib(auth, "mechanism", "PLAIN"); + } if (strchr(client->jid->user, '/')) { char *user = ast_strdupa(client->jid->user); @@ -3285,28 +3312,28 @@ static int xmpp_ping_request(struct ast_xmpp_client *client, const char *to, con { iks *iq, *ping; int res; - + ast_debug(2, "JABBER: Sending Keep-Alive Ping for client '%s'\n", client->name); if (!(iq = iks_new("iq")) || !(ping = iks_new("ping"))) { iks_delete(iq); return -1; } - + iks_insert_attrib(iq, "type", "get"); iks_insert_attrib(iq, "to", to); iks_insert_attrib(iq, "from", from); - + ast_xmpp_client_lock(client); iks_insert_attrib(iq, "id", client->mid); ast_xmpp_increment_mid(client->mid); ast_xmpp_client_unlock(client); - + iks_insert_attrib(ping, "xmlns", "urn:xmpp:ping"); iks_insert_node(iq, ping); - + res = ast_xmpp_client_send(client, iq); - + iks_delete(ping); iks_delete(iq); @@ -3627,6 +3654,13 @@ static int xmpp_client_reconnect(struct ast_xmpp_client *client) return -1; } + if (!ast_strlen_zero(clientcfg->refresh_token)) { + ast_debug(2, "Obtaining OAuth access token for client '%s'\n", client->name); + if (fetch_access_token(clientcfg)) { + return -1; + } + } + ast_xmpp_client_disconnect(client); client->timeout = 50; @@ -3643,7 +3677,7 @@ static int xmpp_client_reconnect(struct ast_xmpp_client *client) /* Set socket timeout options */ setsockopt(iks_fd(client->parser), SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)); - + if (res == IKS_NET_NOCONN) { ast_log(LOG_ERROR, "No XMPP connection available when trying to connect client '%s'\n", client->name); return -1; @@ -3728,7 +3762,7 @@ static int xmpp_client_receive(struct ast_xmpp_client *client, unsigned int time /* Log the message here, because iksemel's logHook is unaccessible */ xmpp_log_hook(client, buf, len, 1); - + if(buf[0] == ' ') { ast_debug(1, "JABBER: Detected Google Keep Alive. " "Sending out Ping request for client '%s'\n", client->name); @@ -3869,6 +3903,42 @@ static int xmpp_client_config_merge_buddies(void *obj, void *arg, int flags) return 1; } +static int fetch_access_token(struct ast_xmpp_client_config *cfg) +{ + RAII_VAR(char *, cmd, NULL, ast_free); + char cBuf[1024] = ""; + const char *url = "https://www.googleapis.com/oauth2/v3/token"; + struct ast_json_error error; + RAII_VAR(struct ast_json *, jobj, NULL, ast_json_unref); + + ast_asprintf(&cmd, "CURL(%s,client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token)", + url, cfg->oauth_clientid, cfg->oauth_secret, cfg->refresh_token); + + ast_debug(2, "Performing OAuth 2.0 authentication for client '%s' using command: %s\n", + cfg->name, cmd); + + if (ast_func_read(NULL, cmd, cBuf, sizeof(cBuf) - 1)) { + ast_log(LOG_ERROR, "CURL is unavailable. This is required for OAuth 2.0 authentication of XMPP client '%s'. Please ensure it is loaded.\n", + cfg->name); + return -1; + } + + ast_debug(2, "OAuth 2.0 authentication for client '%s' returned: %s\n", cfg->name, cBuf); + + jobj = ast_json_load_string(cBuf, &error); + if (jobj) { + const char *token = ast_json_string_get(ast_json_object_get(jobj, "access_token")); + if (token) { + ast_string_field_set(cfg, password, token); + return 0; + } + } + + ast_log(LOG_ERROR, "An error occurred while performing OAuth 2.0 authentication for client '%s': %s\n", cfg->name, cBuf); + + return -1; +} + static int xmpp_client_config_post_apply(void *obj, void *arg, int flags) { struct ast_xmpp_client_config *cfg = obj; @@ -4622,8 +4692,8 @@ static int client_buddy_handler(const struct aco_option *opt, struct ast_variabl * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails - * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the - * configuration file or other non-critical problem return + * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the + * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) @@ -4641,6 +4711,9 @@ static int load_module(void) aco_option_register(&cfg_info, "username", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, user)); aco_option_register(&cfg_info, "secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, password)); + aco_option_register(&cfg_info, "refresh_token", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, refresh_token)); + aco_option_register(&cfg_info, "oauth_clientid", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_clientid)); + aco_option_register(&cfg_info, "oauth_secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_secret)); aco_option_register(&cfg_info, "serverhost", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, server)); aco_option_register(&cfg_info, "statusmessage", ACO_EXACT, client_options, "Online and Available", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, statusmsg)); aco_option_register(&cfg_info, "pubsub_node", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, pubsubnode)); diff --git a/res/srtp/srtp_compat.h b/res/srtp/srtp_compat.h index 4ab39f318..bf4208244 100644 --- a/res/srtp/srtp_compat.h +++ b/res/srtp/srtp_compat.h @@ -5,7 +5,11 @@ #define crypto_policy_t srtp_crypto_policy_t +#if defined(SRTP_AES_ICM_128) +#define AES_128_ICM SRTP_AES_ICM_128 +#else #define AES_128_ICM SRTP_AES_ICM +#endif #define HMAC_SHA1 SRTP_HMAC_SHA1 #define err_status_t srtp_err_status_t diff --git a/res/stasis/control.c b/res/stasis/control.c index b2b076b73..7e8ea91ef 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -927,14 +927,21 @@ static int bridge_channel_depart(struct stasis_app_control *control, return 0; } -static void bridge_after_cb(struct ast_channel *chan, void *data) +static void internal_bridge_after_cb(struct ast_channel *chan, void *data, + enum ast_bridge_after_cb_reason reason) { struct stasis_app_control *control = data; SCOPED_AO2LOCK(lock, control); struct ast_bridge_channel *bridge_channel; - ast_debug(3, "%s, %s: Channel leaving bridge\n", - ast_channel_uniqueid(chan), control->bridge->uniqueid); + ast_debug(3, "%s, %s: %s\n", + ast_channel_uniqueid(chan), control->bridge ? control->bridge->uniqueid : "unknown", + ast_bridge_after_cb_reason_string(reason)); + + if (reason == AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED) { + /* The impart actually failed so control->bridge isn't valid. */ + control->bridge = NULL; + } ast_assert(chan == control->channel); @@ -942,18 +949,21 @@ static void bridge_after_cb(struct ast_channel *chan, void *data) ast_channel_pbx_set(control->channel, control->pbx); control->pbx = NULL; - app_unsubscribe_bridge(control->app, control->bridge); + if (control->bridge) { + app_unsubscribe_bridge(control->app, control->bridge); - /* No longer in the bridge */ - control->bridge = NULL; + /* No longer in the bridge */ + control->bridge = NULL; - /* Get the bridge channel so we don't depart from the wrong bridge */ - ast_channel_lock(chan); - bridge_channel = ast_channel_get_bridge_channel(chan); - ast_channel_unlock(chan); + /* Get the bridge channel so we don't depart from the wrong bridge */ + ast_channel_lock(chan); + bridge_channel = ast_channel_get_bridge_channel(chan); + ast_channel_unlock(chan); + + /* Depart this channel from the bridge using the command queue if possible */ + stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel, __ao2_cleanup); + } - /* Depart this channel from the bridge using the command queue if possible */ - stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel, __ao2_cleanup); if (stasis_app_channel_is_stasis_end_published(chan)) { /* The channel has had a StasisEnd published on it, but until now had remained in * the bridging system. This means that the channel moved from a Stasis bridge to a @@ -971,12 +981,19 @@ static void bridge_after_cb(struct ast_channel *chan, void *data) } } +static void bridge_after_cb(struct ast_channel *chan, void *data) +{ + struct stasis_app_control *control = data; + + internal_bridge_after_cb(control->channel, data, AST_BRIDGE_AFTER_CB_REASON_DEPART); +} + static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason, void *data) { struct stasis_app_control *control = data; - bridge_after_cb(control->channel, data); + internal_bridge_after_cb(control->channel, data, reason); ast_debug(3, " reason: %s\n", ast_bridge_after_cb_reason_string(reason)); @@ -1014,42 +1031,53 @@ int control_swap_channel_in_bridge(struct stasis_app_control *control, struct as return -1; } - { - /* pbx and bridge are modified by the bridging impart thread. - * It shouldn't happen concurrently, but we still need to lock - * for the memory fence. - */ - SCOPED_AO2LOCK(lock, control); + ao2_lock(control); - /* Ensure the controlling application is subscribed early enough - * to receive the ChannelEnteredBridge message. This works in concert - * with the subscription handled in the Stasis application execution - * loop */ - app_subscribe_bridge(control->app, bridge); - - /* Save off the channel's PBX */ - ast_assert(control->pbx == NULL); - if (!control->pbx) { - control->pbx = ast_channel_pbx(chan); - ast_channel_pbx_set(chan, NULL); - } + /* Ensure the controlling application is subscribed early enough + * to receive the ChannelEnteredBridge message. This works in concert + * with the subscription handled in the Stasis application execution + * loop */ + app_subscribe_bridge(control->app, bridge); - res = ast_bridge_impart(bridge, - chan, - swap, - NULL, /* features */ - AST_BRIDGE_IMPART_CHAN_DEPARTABLE); - if (res != 0) { - ast_log(LOG_ERROR, "Error adding channel to bridge\n"); - ast_channel_pbx_set(chan, control->pbx); - control->pbx = NULL; - return -1; - } + /* Save off the channel's PBX */ + ast_assert(control->pbx == NULL); + if (!control->pbx) { + control->pbx = ast_channel_pbx(chan); + ast_channel_pbx_set(chan, NULL); + } - ast_assert(stasis_app_get_bridge(control) == NULL); - control->bridge = bridge; + ast_assert(stasis_app_get_bridge(control) == NULL); + /* We need to set control->bridge here since bridge_after_cb may be run + * before ast_bridge_impart returns. bridge_after_cb gets a reason + * code so it can tell if the bridge is actually valid or not. + */ + control->bridge = bridge; + + /* We can't be holding the control lock while impart is running + * or we could create a deadlock with bridge_after_cb which also + * tries to lock control. + */ + ao2_unlock(control); + res = ast_bridge_impart(bridge, + chan, + swap, + NULL, /* features */ + AST_BRIDGE_IMPART_CHAN_DEPARTABLE); + if (res != 0) { + /* ast_bridge_impart failed before it could spawn the depart + * thread. The callbacks aren't called in this case. + * The impart could still fail even if ast_bridge_impart returned + * ok but that's handled by bridge_after_cb. + */ + ast_log(LOG_ERROR, "Error adding channel to bridge\n"); + ao2_lock(control); + ast_channel_pbx_set(chan, control->pbx); + control->pbx = NULL; + control->bridge = NULL; + ao2_unlock(control); } - return 0; + + return res; } int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data) |