summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES11
-rw-r--r--UPGRADE.txt9
-rw-r--r--apps/app_queue.c20
-rw-r--r--apps/app_transfer.c2
-rw-r--r--configs/samples/rtp.conf.sample14
-rw-r--r--include/asterisk/res_pjsip.h4
-rw-r--r--include/asterisk/rtp_engine.h10
-rw-r--r--main/rtp_engine.c19
-rw-r--r--main/stasis_endpoints.c2
-rw-r--r--res/res_clialiases.c8
-rw-r--r--res/res_hep.c2
-rw-r--r--res/res_musiconhold.c4
-rw-r--r--res/res_pjsip.c1
-rw-r--r--res/res_pjsip/pjsip_options.c32
-rw-r--r--res/res_rtp_asterisk.c45
-rw-r--r--res/res_smdi.c50
-rw-r--r--rest-api/api-docs/events.json2
-rw-r--r--third-party/pjproject/patches/0050-dont_terminate_session_early.patch71
18 files changed, 244 insertions, 62 deletions
diff --git a/CHANGES b/CHANGES
index b2b740924..f367f46cf 100644
--- a/CHANGES
+++ b/CHANGES
@@ -26,6 +26,17 @@ chan_sip
headers be retrieved from the REFER message and made accessible to the
dialplan in the hash TRANSFER_DATA.
+AMI
+------------------
+ * The ContactStatus and Status fields for the manager events ContactStatus
+ and ContactStatusDetail are now set to "NonQualified" when a contact exists
+ but has not been qualified.
+
+ARI
+------------------
+ * The ContactInfo event's contact_status field is now set to "NonQualified"
+ when a contact exists but has not been qualified.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 15.1.0 to Asterisk 15.2.0 ------------
------------------------------------------------------------------------------
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 39c0f8cec..dd37b259d 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -31,6 +31,15 @@ app_macro:
built. Users should migrate to app_stack (Gosub). A warning is logged
the first time any Macro is used.
+AMI:
+ - The ContactStatus and Status fields for the manager events ContactStatus
+ and ContactStatusDetail are now set to "NonQualified" when a contact exists
+ but has not been qualified.
+
+ARI:
+ - The ContactInfo event's contact_status field is now set to "NonQualified"
+ when a contact exists but has not been qualified.
+
New in 15.0.0:
Build System:
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 801695fff..a140847c6 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1575,6 +1575,7 @@ struct member {
char state_exten[AST_MAX_EXTENSION]; /*!< Extension to get state from (if using hint) */
char state_context[AST_MAX_CONTEXT]; /*!< Context to use when getting state (if using hint) */
char state_interface[AST_CHANNEL_NAME]; /*!< Technology/Location from which to read devicestate changes */
+ int state_id; /*!< Extension state callback id (if using hint) */
char membername[80]; /*!< Member name to use in queue logs */
int penalty; /*!< Are we a last resort? */
int calls; /*!< Number of calls serviced by this member */
@@ -2632,12 +2633,21 @@ static int get_queue_member_status(struct member *cur)
return ast_strlen_zero(cur->state_exten) ? ast_device_state(cur->state_interface) : extensionstate2devicestate(ast_extension_state(NULL, cur->state_context, cur->state_exten));
}
+static void destroy_queue_member_cb(void *obj)
+{
+ struct member *mem = obj;
+
+ if (mem->state_id != -1) {
+ ast_extension_state_del(mem->state_id, extension_state_cb);
+ }
+}
+
/*! \brief allocate space for new queue member and set fields based on parameters passed */
static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse)
{
struct member *cur;
- if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
+ if ((cur = ao2_alloc(sizeof(*cur), destroy_queue_member_cb))) {
cur->ringinuse = ringinuse;
cur->penalty = penalty;
cur->paused = paused;
@@ -2664,6 +2674,10 @@ static struct member *create_queue_member(const char *interface, const char *mem
ast_copy_string(cur->state_exten, exten, sizeof(cur->state_exten));
ast_copy_string(cur->state_context, S_OR(context, "default"), sizeof(cur->state_context));
+
+ cur->state_id = ast_extension_state_add(cur->state_context, cur->state_exten, extension_state_cb, NULL);
+ } else {
+ cur->state_id = -1;
}
cur->status = get_queue_member_status(cur);
}
@@ -11084,8 +11098,6 @@ static int unload_module(void)
device_state_sub = stasis_unsubscribe_and_join(device_state_sub);
- ast_extension_state_del(0, extension_state_cb);
-
ast_unload_realtime("queue_members");
ao2_cleanup(queues);
ao2_cleanup(pending_members);
@@ -11243,8 +11255,6 @@ static int load_module(void)
err |= STASIS_MESSAGE_TYPE_INIT(queue_agent_dump_type);
err |= STASIS_MESSAGE_TYPE_INIT(queue_agent_ringnoanswer_type);
- ast_extension_state_add(NULL, NULL, extension_state_cb, NULL);
-
if (err) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
diff --git a/apps/app_transfer.c b/apps/app_transfer.c
index 7bc67e154..1d7c87920 100644
--- a/apps/app_transfer.c
+++ b/apps/app_transfer.c
@@ -51,7 +51,7 @@
</syntax>
<description>
<para>Requests the remote caller be transferred
- to a given destination. If TECH (SIP, IAX2, LOCAL etc) is used, only
+ to a given destination. If TECH (SIP, IAX2, etc) is used, only
an incoming call with the same channel technology will be transferred.
Note that for SIP, if you transfer before call is setup, a 302 redirect
SIP message will be returned to the caller.</para>
diff --git a/configs/samples/rtp.conf.sample b/configs/samples/rtp.conf.sample
index 9bc3de3cf..de9d59007 100644
--- a/configs/samples/rtp.conf.sample
+++ b/configs/samples/rtp.conf.sample
@@ -21,9 +21,17 @@ rtpend=20000
; rtcpinterval = 5000 ; Milliseconds between rtcp reports
;(min 500, max 60000, default 5000)
;
-; Enable strict RTP protection. This will drop RTP packets that
-; do not come from the source of the RTP stream. This option is
-; enabled by default.
+; Enable strict RTP protection. This will drop RTP packets that do not come
+; from the recoginized source of the RTP stream. Strict RTP qualifies RTP
+; packet stream sources before accepting them upon initial connection and
+; when the connection is renegotiated (e.g., transfers and direct media).
+; Initial connection and renegotiation starts a learning mode to qualify
+; stream source addresses. Once Asterisk has recognized a stream it will
+; allow other streams to qualify and replace the current stream for 5
+; seconds after starting learning mode. Once learning mode completes the
+; current stream is locked in and cannot change until the next
+; renegotiation.
+; This option is enabled by default.
; strictrtp=yes
;
; Number of packets containing consecutive sequence values needed
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index e71eb98d7..6c48d2e84 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -289,9 +289,13 @@ struct ast_sip_contact {
* \brief Status type for a contact.
*/
enum ast_sip_contact_status_type {
+ /*! Frequency > 0, but no response from remote uri */
UNAVAILABLE,
+ /*! Frequency > 0, and got response from remote uri */
AVAILABLE,
+ /*! Default last status, and when a contact status object is not found */
UNKNOWN,
+ /*! Frequency == 0, has a contact, but don't know status (non-qualified) */
CREATED,
REMOVED,
};
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index f9d686aca..c77be4584 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -1383,6 +1383,16 @@ int ast_rtp_codecs_payloads_set_rtpmap_type_rate(struct ast_rtp_codecs *codecs,
void ast_rtp_codecs_payloads_unset(struct ast_rtp_codecs *codecs, struct ast_rtp_instance *instance, int payload);
/*!
+ * \brief Determine the type of RTP stream media from the codecs mapped.
+ * \since 13.19.0
+ *
+ * \param codecs Codecs structure to look in
+ *
+ * \return Media type or AST_MEDIA_TYPE_UNKNOWN if no codecs mapped.
+ */
+enum ast_media_type ast_rtp_codecs_get_stream_type(struct ast_rtp_codecs *codecs);
+
+/*!
* \brief Retrieve rx payload mapped information by payload type
*
* \param codecs Codecs structure to look in
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 2431ffc0c..68c53e7ff 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -1176,6 +1176,25 @@ void ast_rtp_codecs_payloads_unset(struct ast_rtp_codecs *codecs, struct ast_rtp
ast_rwlock_unlock(&codecs->codecs_lock);
}
+enum ast_media_type ast_rtp_codecs_get_stream_type(struct ast_rtp_codecs *codecs)
+{
+ enum ast_media_type stream_type = AST_MEDIA_TYPE_UNKNOWN;
+ int payload;
+ struct ast_rtp_payload_type *type;
+
+ ast_rwlock_rdlock(&codecs->codecs_lock);
+ for (payload = 0; payload < AST_VECTOR_SIZE(&codecs->payload_mapping_rx); ++payload) {
+ type = AST_VECTOR_GET(&codecs->payload_mapping_rx, payload);
+ if (type && type->asterisk_format) {
+ stream_type = ast_format_get_type(type->format);
+ break;
+ }
+ }
+ ast_rwlock_unlock(&codecs->codecs_lock);
+
+ return stream_type;
+}
+
struct ast_rtp_payload_type *ast_rtp_codecs_get_payload(struct ast_rtp_codecs *codecs, int payload)
{
struct ast_rtp_payload_type *type = NULL;
diff --git a/main/stasis_endpoints.c b/main/stasis_endpoints.c
index 161fdfa44..5cee22eba 100644
--- a/main/stasis_endpoints.c
+++ b/main/stasis_endpoints.c
@@ -82,7 +82,7 @@
<enum name="Unknown"/>
<enum name="Unreachable"/>
<enum name="Reachable"/>
- <enum name="Created"/>
+ <enum name="Unqualified"/>
<enum name="Removed"/>
<enum name="Updated"/>
</enumlist>
diff --git a/res/res_clialiases.c b/res/res_clialiases.c
index 1a2fc6939..337c31c97 100644
--- a/res/res_clialiases.c
+++ b/res/res_clialiases.c
@@ -103,7 +103,7 @@ static char *cli_alias_passthrough(struct ast_cli_entry *e, int cmd, struct ast_
struct cli_alias tmp = {
.cli_entry.command = e->command,
};
- char *generator;
+ char *generator = NULL;
const char *line;
/* Try to find the alias based on the CLI entry */
@@ -118,14 +118,10 @@ static char *cli_alias_passthrough(struct ast_cli_entry *e, int cmd, struct ast_
case CLI_GENERATE:
line = a->line;
line += (strlen(alias->alias));
- if (!strncasecmp(alias->alias, alias->real_cmd, strlen(alias->alias))) {
- generator = NULL;
- } else if (!ast_strlen_zero(a->word)) {
+ if (strncasecmp(alias->alias, alias->real_cmd, strlen(alias->alias))) {
struct ast_str *real_cmd = ast_str_alloca(strlen(alias->real_cmd) + strlen(line) + 1);
ast_str_append(&real_cmd, 0, "%s%s", alias->real_cmd, line);
generator = ast_cli_generator(ast_str_buffer(real_cmd), a->word, a->n);
- } else {
- generator = ast_cli_generator(alias->real_cmd, a->word, a->n);
}
ao2_ref(alias, -1);
return generator;
diff --git a/res/res_hep.c b/res/res_hep.c
index 25b4d13b1..ba036d70f 100644
--- a/res/res_hep.c
+++ b/res/res_hep.c
@@ -421,7 +421,7 @@ int hepv3_is_loaded(void)
{
RAII_VAR(struct module_config *, config, ao2_global_obj_ref(global_config), ao2_cleanup);
- return (config != NULL) ? 1 : 0;
+ return config && config->general->enabled;
}
struct hepv3_capture_info *hepv3_create_capture_info(const void *payload, size_t len)
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index ef1b81c2a..17e91b70c 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -333,6 +333,7 @@ static int ast_moh_files_next(struct ast_channel *chan)
}
} else {
state->announcement = 0;
+ state->samples = 0;
}
if (!state->class->total_files) {
@@ -1934,6 +1935,9 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc
ast_cli(a->fd, "Class: %s\n", class->name);
ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
+ if (ast_test_flag(class, MOH_ANNOUNCEMENT)) {
+ ast_cli(a->fd, "\tAnnouncement: %s\n", S_OR(class->announcement, "<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);
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 21e43f073..b50ee5fc7 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -2282,6 +2282,7 @@
<enumlist>
<enum name="Reachable"/>
<enum name="Unreachable"/>
+ <enum name="NonQualified"/>
</enumlist>
</parameter>
<parameter name="RoundtripUsec">
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index fbe07d53d..f9df2f0a5 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -41,7 +41,7 @@ static const char *status_map [] = {
[UNAVAILABLE] = "Unreachable",
[AVAILABLE] = "Reachable",
[UNKNOWN] = "Unknown",
- [CREATED] = "Created",
+ [CREATED] = "NonQualified",
[REMOVED] = "Removed",
};
@@ -49,7 +49,7 @@ static const char *short_status_map [] = {
[UNAVAILABLE] = "Unavail",
[AVAILABLE] = "Avail",
[UNKNOWN] = "Unknown",
- [CREATED] = "Created",
+ [CREATED] = "NonQual",
[REMOVED] = "Removed",
};
@@ -205,24 +205,12 @@ static void update_contact_status(const struct ast_sip_contact *contact,
return;
}
- if (is_contact_refresh
- && status->status == CREATED) {
- /*
- * The contact status hasn't been updated since creation
- * and we don't want to re-send a created status.
- */
- if (contact->qualify_frequency
- || status->rtt_start.tv_sec > 0) {
- /* Ignore, the status will change soon. */
- return;
- }
-
- /*
- * Convert to a regular contact status update
- * because the status may never change.
- */
- is_contact_refresh = 0;
- value = UNKNOWN;
+ /*
+ * If the current status is CREATED, and it's a refresh or the given value is
+ * also CREATED then there is nothing to update as nothing needs to change.
+ */
+ if (status->status == CREATED && (is_contact_refresh || status->status == value)) {
+ return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
@@ -595,7 +583,7 @@ static void qualify_and_schedule(struct ast_sip_contact *contact)
schedule_qualify(contact, contact->qualify_frequency * 1000);
} else {
- update_contact_status(contact, UNKNOWN, 0);
+ update_contact_status(contact, CREATED, 0);
}
}
@@ -1127,7 +1115,7 @@ static void qualify_and_schedule_contact(struct ast_sip_contact *contact)
if (contact->qualify_frequency) {
schedule_qualify(contact, initial_interval);
} else {
- update_contact_status(contact, UNKNOWN, 0);
+ update_contact_status(contact, CREATED, 0);
}
}
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index bdc83301e..51e509c77 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -256,6 +256,8 @@ struct rtp_learning_info {
struct timeval received; /*!< The time of the first received packet */
int max_seq; /*!< The highest sequence number received */
int packets; /*!< The number of remaining packets before the source is accepted */
+ /*! Type of media stream carried by the RTP instance */
+ enum ast_media_type stream_type;
};
#ifdef HAVE_OPENSSL_SRTP
@@ -3095,18 +3097,30 @@ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t
info->received = ast_tvnow();
}
- /*
- * Protect against packet floods by checking that we
- * received the packet sequence in at least the minimum
- * allowed time.
- */
- if (ast_tvzero(info->received)) {
- info->received = ast_tvnow();
- } else if (!info->packets && (ast_tvdiff_ms(ast_tvnow(), info->received) < learning_min_duration )) {
- /* Packet flood; reset */
- info->packets = learning_min_sequential - 1;
- info->received = ast_tvnow();
+ switch (info->stream_type) {
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_AUDIO:
+ /*
+ * Protect against packet floods by checking that we
+ * received the packet sequence in at least the minimum
+ * allowed time.
+ */
+ if (ast_tvzero(info->received)) {
+ info->received = ast_tvnow();
+ } else if (!info->packets
+ && ast_tvdiff_ms(ast_tvnow(), info->received) < learning_min_duration) {
+ /* Packet flood; reset */
+ info->packets = learning_min_sequential - 1;
+ info->received = ast_tvnow();
+ }
+ break;
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
}
+
info->max_seq = seq;
return info->packets;
@@ -5951,6 +5965,15 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
* source and we should switch to it.
*/
if (!ast_sockaddr_cmp(&rtp->rtp_source_learn.proposed_address, &addr)) {
+ if (rtp->rtp_source_learn.stream_type == AST_MEDIA_TYPE_UNKNOWN) {
+ struct ast_rtp_codecs *codecs;
+
+ codecs = ast_rtp_instance_get_codecs(instance);
+ rtp->rtp_source_learn.stream_type =
+ ast_rtp_codecs_get_stream_type(codecs);
+ ast_verb(4, "%p -- Strict RTP qualifying stream type: %s\n",
+ rtp, ast_codec_media_type2str(rtp->rtp_source_learn.stream_type));
+ }
if (!rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) {
/* Accept the new RTP stream */
ast_verb(4, "%p -- Strict RTP switching source address to %s\n",
diff --git a/res/res_smdi.c b/res/res_smdi.c
index 0edabb83c..612a4b87b 100644
--- a/res/res_smdi.c
+++ b/res/res_smdi.c
@@ -586,9 +586,8 @@ static void *smdi_read(void *iface_p)
struct ast_smdi_interface *iface = iface_p;
struct ast_smdi_md_message *md_msg;
struct ast_smdi_mwi_message *mwi_msg;
- char c = '\0';
char *cp = NULL;
- int i;
+ int i, c;
int start = 0;
/* read an smdi message */
@@ -616,7 +615,14 @@ static void *smdi_read(void *iface_p)
/* 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);
+ c = fgetc(iface->file);
+ if (c == EOF) {
+ ast_log(LOG_ERROR, "Unexpected EOF while reading MD message\n");
+ ao2_ref(md_msg, -1);
+ ao2_ref(iface, -1);
+ return NULL;
+ }
+ md_msg->mesg_desk_num[i] = (char) c;
ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
}
@@ -626,7 +632,14 @@ static void *smdi_read(void *iface_p)
/* read the message desk terminal number */
for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
- md_msg->mesg_desk_term[i] = fgetc(iface->file);
+ c = fgetc(iface->file);
+ if (c == EOF) {
+ ast_log(LOG_ERROR, "Unexpected EOF while reading SMDI message\n");
+ ao2_ref(md_msg, -1);
+ ao2_ref(iface, -1);
+ return NULL;
+ }
+ md_msg->mesg_desk_term[i] = (char) c;
ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
}
@@ -635,7 +648,14 @@ static void *smdi_read(void *iface_p)
ast_debug(1, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
/* read the message type */
- md_msg->type = fgetc(iface->file);
+ c = fgetc(iface->file);
+ if (c == EOF) {
+ ast_log(LOG_ERROR, "Unexpected EOF while reading SMDI message\n");
+ ao2_ref(md_msg, -1);
+ ao2_ref(iface, -1);
+ return NULL;
+ }
+ md_msg->type = (char) c;
ast_debug(1, "Message type is '%c'\n", md_msg->type);
@@ -717,7 +737,7 @@ static void *smdi_read(void *iface_p)
/* discard the 'I' (from 'MWI') */
fgetc(iface->file);
-
+
/* read the forwarding station number (may be blank) */
cp = &mwi_msg->fwd_st[0];
for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
@@ -740,8 +760,16 @@ static void *smdi_read(void *iface_p)
ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
/* read the mwi failure cause */
- for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
- mwi_msg->cause[i] = fgetc(iface->file);
+ for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++) {
+ c = fgetc(iface->file);
+ if (c == EOF) {
+ ast_log(LOG_ERROR, "Unexpected EOF while reading MWI message\n");
+ ao2_ref(mwi_msg, -1);
+ ao2_ref(iface, -1);
+ return NULL;
+ }
+ mwi_msg->cause[i] = (char) c;
+ }
mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
@@ -1377,6 +1405,9 @@ static int load_module(void)
ast_custom_function_register(&smdi_msg_retrieve_function);
ast_custom_function_register(&smdi_msg_function);
+ /* For Optional API. */
+ ast_module_shutdown_ref(AST_MODULE_SELF);
+
return AST_MODULE_LOAD_SUCCESS;
}
@@ -1406,9 +1437,6 @@ static int _unload_module(int fromload)
smdi_loaded = 0;
- /* For Optional API. */
- ast_module_shutdown_ref(AST_MODULE_SELF);
-
return 0;
}
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 9ebbac061..e1b31bb97 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -207,7 +207,7 @@
"Unreachable",
"Reachable",
"Unknown",
- "Created",
+ "NonQualified",
"Removed"
]
}
diff --git a/third-party/pjproject/patches/0050-dont_terminate_session_early.patch b/third-party/pjproject/patches/0050-dont_terminate_session_early.patch
new file mode 100644
index 000000000..718968c79
--- /dev/null
+++ b/third-party/pjproject/patches/0050-dont_terminate_session_early.patch
@@ -0,0 +1,71 @@
+commit ca0b723e92bd76bbda1bbd14477a829eaeeb675e
+Author: Joshua Colp <jcolp@digium.com>
+Date: Wed Dec 13 10:58:57 2017 +0000
+
+ Ignore transport error on completed transaction.
+ Don't disconnect call if transport error happens on transaction that is not initial INVITE transaction.
+
+ Scenario:
+
+ DNS lookup returning two servers.
+ Sending INVITE to first server over TCP.
+ Response received with code 503 (Service Unavailable).
+ Failover to second server, sending second INVITE after restarting the session.
+ TCP connection for the first INVITE getting disconnected and causing call disconnection (while second INVITE is still outstanding).
+
+ This is a backport of 5714 from upstream PJSIP.
+
+diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
+index ac4d1949..0173cb4c 100644
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -4254,8 +4254,7 @@ static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e)
+ if ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST &&
+ tsx->method.id != PJSIP_CANCEL_METHOD) ||
+ tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
+- tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
+- tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
++ tsx->status_code == PJSIP_SC_TSX_TIMEOUT)
+ {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
+index 7ac3d1b7..d52b12a7 100644
+--- a/pjsip/src/pjsip/sip_transaction.c
++++ b/pjsip/src/pjsip/sip_transaction.c
+@@ -2044,9 +2044,14 @@ static void transport_callback(void *token, pjsip_tx_data *tdata,
+ */
+ lock_timer(tsx);
+ tsx->transport_err = (pj_status_t)-sent;
+- tsx_cancel_timer(tsx, &tsx->timeout_timer);
+- tsx_schedule_timer(tsx, &tsx->timeout_timer, &delay,
+- TRANSPORT_ERR_TIMER);
++ /* Don't cancel timeout timer if tsx state is already
++ * PJSIP_TSX_STATE_COMPLETED (see #2076).
++ */
++ if (tsx->state < PJSIP_TSX_STATE_COMPLETED) {
++ tsx_cancel_timer(tsx, &tsx->timeout_timer);
++ tsx_schedule_timer(tsx, &tsx->timeout_timer, &delay,
++ TRANSPORT_ERR_TIMER);
++ }
+ unlock_timer(tsx);
+ }
+
+@@ -2077,9 +2082,14 @@ static void tsx_tp_state_callback( pjsip_transport *tp,
+ */
+ lock_timer(tsx);
+ tsx->transport_err = info->status;
+- tsx_cancel_timer(tsx, &tsx->timeout_timer);
+- tsx_schedule_timer(tsx, &tsx->timeout_timer, &delay,
+- TRANSPORT_ERR_TIMER);
++ /* Don't cancel timeout timer if tsx state is already
++ * PJSIP_TSX_STATE_COMPLETED (see #2076).
++ */
++ if (tsx->state < PJSIP_TSX_STATE_COMPLETED) {
++ tsx_cancel_timer(tsx, &tsx->timeout_timer);
++ tsx_schedule_timer(tsx, &tsx->timeout_timer, &delay,
++ TRANSPORT_ERR_TIMER);
++ }
+ unlock_timer(tsx);
+ }
+ }