Merge "res_pjsip/config_transport: Allow reloading transports."
authorJoshua Colp <jcolp@digium.com>
Sat, 27 Feb 2016 16:18:26 +0000 (10:18 -0600)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Sat, 27 Feb 2016 16:18:26 +0000 (10:18 -0600)
CHANGES
configs/samples/pjsip.conf.sample
contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py [new file with mode: 0644]
include/asterisk/res_pjsip.h
res/res_pjsip.c
res/res_pjsip/config_transport.c
res/res_pjsip_outbound_registration.c
res/res_pjsip_pubsub.c
res/res_pjsip_session.c

diff --git a/CHANGES b/CHANGES
index 7b1dfb6..91e170f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -250,6 +250,13 @@ res_pjproject
 
 res_pjsip
 ------------------
+ * Transports are now reloadable.  In testing, no in-progress calls were
+   disrupted if the ip address or port weren't changed, but the possibility
+   still exists.  To make sure there are no unintentional drops, a new option
+   'allow_reload', which defaults to 'no' has been added to transport.  If
+   left at the default, changes to the particular transport will be ignored.
+   If set to 'yes', changes (if any) will be applied.
+
  * Added new global option (regcontext) to pjsip. When set, Asterisk will
    dynamically create and destroy a NoOp priority 1 extension
    for a given endpoint who registers or unregisters with us.
index 5c326f2..2d127a1 100644 (file)
                                 ; clients are slow to process the received
                                 ; information. Value is in milliseconds; default
                                 ; is 100 ms.
+;allow_reload=no    ; Although transports can now be reloaded, that may not be
+                    ; desirable because of the slight possibility of dropped
+                    ; calls. To make sure there are no unintentional drops, if
+                    ; this option is set to 'no' (the default) changes to the
+                    ; particular transport will be ignored. If set to 'yes',
+                    ; changes (if any) will be applied.
 
 ;==========================AOR SECTION OPTIONS=========================
 ;[aor]
diff --git a/contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py b/contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py
new file mode 100644 (file)
index 0000000..377179b
--- /dev/null
@@ -0,0 +1,27 @@
+"""Add allow_reload to ps_transports
+
+Revision ID: 3bcc0b5bc2c9
+Revises: dbc44d5a908
+Create Date: 2016-02-05 17:43:39.183785
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '3bcc0b5bc2c9'
+down_revision = 'dbc44d5a908'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+    op.add_column('ps_transports', sa.Column('allow_reload', yesno_values))
+    pass
+
+def downgrade():
+    op.drop_column('ps_transports', 'allow_reload')
+    pass
index ad34c8f..3008475 100644 (file)
@@ -185,6 +185,8 @@ struct ast_sip_transport {
        unsigned int cos;
        /*! Write timeout */
        int write_timeout;
+       /*! Allow reload */
+       int allow_reload;
 };
 
 #define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias"
@@ -2223,4 +2225,26 @@ struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transpor
  */
 struct ao2_container *ast_sip_get_transport_states(void);
 
+/*!
+ * \brief Sets pjsip_tpselector from ast_sip_transport
+ * \since 13.8.0
+ *
+ * \param transport The transport to be used
+ * \param selector The selector to be populated
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector);
+
+/*!
+ * \brief Sets pjsip_tpselector from ast_sip_transport
+ * \since 13.8.0
+ *
+ * \param transport_name The name of the transport to be used
+ * \param selector The selector to be populated
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector);
+
 #endif /* _RES_PJSIP_H */
index e355292..713d94e 100644 (file)
                                                Value is in milliseconds; default is 100 ms.</para>
                                        </description>
                                </configOption>
+                               <configOption name="allow_reload" default="no">
+                                       <synopsis>Allow this transport to be reloaded.</synopsis>
+                                       <description>
+                                               <para>Allow this transport to be reloaded when res_pjsip is reloaded.
+                                               This option defaults to "no" because reloading a transport may disrupt
+                                               in-progress calls.</para>
+                                       </description>
+                               </configOption>
                        </configObject>
                        <configObject name="contact">
                                <synopsis>A way of creating an aliased name to a SIP URI</synopsis>
@@ -2479,22 +2487,14 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u
        return 0;
 }
 
-static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpoint, pjsip_tpselector *selector)
+int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector)
 {
-       RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
        RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
-       const char *transport_name = endpoint->transport;
-
-       if (ast_strlen_zero(transport_name)) {
-               return 0;
-       }
-
-       transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_name);
-       transport_state = ast_sip_get_transport_state(transport_name);
 
-       if (!transport || !transport_state) {
-               ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' for endpoint '%s'\n",
-                       transport_name, ast_sorcery_object_get_id(endpoint));
+       transport_state = ast_sip_get_transport_state(ast_sorcery_object_get_id(transport));
+       if (!transport_state) {
+               ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport state for '%s'\n",
+                       ast_sorcery_object_get_id(transport));
                return -1;
        }
 
@@ -2517,6 +2517,35 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo
        return 0;
 }
 
+int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector)
+{
+       RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+
+       if (ast_strlen_zero(transport_name)) {
+               return 0;
+       }
+
+       transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_name);
+       if (!transport) {
+               ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s'\n",
+                       transport_name);
+               return -1;
+       }
+
+       return ast_sip_set_tpselector_from_transport(transport, selector);
+}
+
+static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpoint, pjsip_tpselector *selector)
+{
+       const char *transport_name = endpoint->transport;
+
+       if (ast_strlen_zero(transport_name)) {
+               return 0;
+       }
+
+       return ast_sip_set_tpselector_from_transport_name(endpoint->transport, selector);
+}
+
 void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t *pool, pjsip_uri *uri)
 {
        pjsip_sip_uri *sip_uri;
index 0fcd7d9..e7bda5f 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "asterisk.h"
 
+#include <math.h>
 #include <pjsip.h>
 #include <pjlib.h>
 
@@ -347,6 +348,44 @@ static void copy_state_to_transport(struct ast_sip_transport *transport)
        memcpy(&transport->external_address, &transport->state->external_address, sizeof(transport->external_address));
 }
 
+static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_transport_state *b)
+{
+       if (a->type != b->type) {
+               return -1;
+       }
+
+       if (pj_sockaddr_cmp(&a->host, &b->host)) {
+               return -1;
+       }
+
+       if ((a->localnet || b->localnet)
+               && ((!a->localnet != !b->localnet)
+               || ast_sockaddr_cmp(&a->localnet->addr, &b->localnet->addr)
+               || ast_sockaddr_cmp(&a->localnet->netmask, &b->localnet->netmask)))
+       {
+               return -1;
+       }
+
+       if (ast_sockaddr_cmp(&a->external_address, &b->external_address)) {
+               return -1;
+       }
+
+       if (a->tls.method != b->tls.method
+               || a->tls.ciphers_num != b->tls.ciphers_num
+               || a->tls.proto != b->tls.proto
+               || a->tls.verify_client != b->tls.verify_client
+               || a->tls.verify_server != b->tls.verify_server
+               || a->tls.require_client_cert != b->tls.require_client_cert) {
+               return -1;
+       }
+
+       if (memcmp(a->ciphers, b->ciphers, sizeof(pj_ssl_cipher) * fmax(a->tls.ciphers_num, b->tls.ciphers_num))) {
+               return -1;
+       }
+
+       return 0;
+}
+
 static void states_cleanup(void *states)
 {
        if (states) {
@@ -364,6 +403,9 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
        RAII_VAR(struct internal_state *, perm_state, NULL, ao2_cleanup);
        RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy);
        pj_status_t res = -1;
+       int i;
+#define BIND_TRIES 3
+#define BIND_DELAY_US 100000
 
        if (!states) {
                return -1;
@@ -376,32 +418,39 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
         */
        ao2_wrlock(states);
 
+       temp_state = internal_state_alloc(transport);
+       if (!temp_state) {
+               ast_log(LOG_ERROR, "Transport '%s' failed to allocate memory\n", transport_id);
+               return -1;
+       }
+
        perm_state = find_internal_state_by_transport(transport);
        if (perm_state) {
                ast_sorcery_diff(sorcery, perm_state->transport, transport, &changes);
-               if (changes) {
+               if (!changes && !has_state_changed(perm_state->state, temp_state->state)) {
+                       /* In case someone is using the deprecated fields, reset them */
+                       transport->state = perm_state->state;
+                       copy_state_to_transport(transport);
+                       ao2_replace(perm_state->transport, transport);
+                       return 0;
+               }
+
+               if (!transport->allow_reload) {
                        if (!perm_state->change_detected) {
                                perm_state->change_detected = 1;
                                ast_log(LOG_WARNING, "Transport '%s' is not reloadable, maintaining previous values\n", transport_id);
                        }
+                       /* In case someone is using the deprecated fields, reset them */
+                       transport->state = perm_state->state;
+                       copy_state_to_transport(transport);
+                       ao2_replace(perm_state->transport, transport);
+                       return 0;
                }
-
-               /* In case someone is using the deprecated fields, reset them */
-               transport->state = perm_state->state;
-               copy_state_to_transport(transport);
-               ao2_replace(perm_state->transport, transport);
-               return 0;
-       }
-
-       temp_state = internal_state_alloc(transport);
-       if (!temp_state) {
-               ast_log(LOG_ERROR, "Transport '%s' failed to allocate memory\n", transport_id);
-               goto error;
        }
 
        if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) {
                ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id);
-               goto error;
+               return -1;
        }
 
        /* Set default port if not present */
@@ -418,20 +467,33 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
                } else {
                        ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n",
                                        transport_id);
-                       goto error;
+                       return -1;
                }
 
                if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_address, &temp_state->state->external_address_refresher, NULL) < 0) {
                        ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", transport_id);
-                       goto error;
+                       return -1;
                }
        }
 
        if (transport->type == AST_TRANSPORT_UDP) {
-               if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
-                       res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(), &temp_state->state->host.ipv4, NULL, transport->async_operations, &temp_state->state->transport);
-               } else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) {
-                       res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(), &temp_state->state->host.ipv6, NULL, transport->async_operations, &temp_state->state->transport);
+
+               for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+                       if (perm_state && perm_state->state && perm_state->state->transport) {
+                               pjsip_udp_transport_pause(perm_state->state->transport,
+                                       PJSIP_UDP_TRANSPORT_DESTROY_SOCKET);
+                               usleep(BIND_DELAY_US);
+                       }
+
+                       if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
+                               res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(),
+                                       &temp_state->state->host.ipv4, NULL, transport->async_operations,
+                                       &temp_state->state->transport);
+                       } else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) {
+                               res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(),
+                                       &temp_state->state->host.ipv6, NULL, transport->async_operations,
+                                       &temp_state->state->transport);
+                       }
                }
 
                if (res == PJ_SUCCESS && (transport->tos || transport->cos)) {
@@ -451,18 +513,37 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
                cfg.async_cnt = transport->async_operations;
                set_qos(transport, &cfg.qos_params);
 
-               res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &temp_state->state->factory);
+               for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+                       if (perm_state && perm_state->state && perm_state->state->factory
+                               && perm_state->state->factory->destroy) {
+                               perm_state->state->factory->destroy(perm_state->state->factory);
+                               usleep(BIND_DELAY_US);
+                       }
+
+                       res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg,
+                               &temp_state->state->factory);
+               }
        } else if (transport->type == AST_TRANSPORT_TLS) {
                if (transport->async_operations > 1 && ast_compare_versions(pj_get_version(), "2.5.0") < 0) {
                        ast_log(LOG_ERROR, "Transport: %s: When protocol=tls and pjproject version < 2.5.0, async_operations can't be > 1\n",
                                        ast_sorcery_object_get_id(obj));
-                       goto error;
+                       return -1;
                }
 
                temp_state->state->tls.password = pj_str((char*)transport->password);
                set_qos(transport, &temp_state->state->tls.qos_params);
 
-               res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &temp_state->state->tls, &temp_state->state->host, NULL, transport->async_operations, &temp_state->state->factory);
+               for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+                       if (perm_state && perm_state->state && perm_state->state->factory
+                               && perm_state->state->factory->destroy) {
+                               perm_state->state->factory->destroy(perm_state->state->factory);
+                               usleep(BIND_DELAY_US);
+                       }
+
+                       res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &temp_state->state->tls,
+                               &temp_state->state->host, NULL, transport->async_operations,
+                               &temp_state->state->factory);
+               }
        } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) {
                if (transport->cos || transport->tos) {
                        ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n");
@@ -475,17 +556,16 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 
                pj_strerror(res, msg, sizeof(msg));
                ast_log(LOG_ERROR, "Transport '%s' could not be started: %s\n", ast_sorcery_object_get_id(obj), msg);
-               goto error;
+               return -1;
        }
 
        copy_state_to_transport(transport);
-       ao2_link(states, temp_state);
+       if (perm_state) {
+               ao2_unlink_flags(states, perm_state, OBJ_NOLOCK);
+       }
+       ao2_link_flags(states, temp_state, OBJ_NOLOCK);
 
        return 0;
-
-error:
-       ao2_unlink(states, temp_state);
-       return -1;
 }
 
 /*! \brief Custom handler for type just makes sure the state is created */
@@ -1209,6 +1289,7 @@ int ast_sip_initialize_sorcery_transport(void)
        ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
        ast_sorcery_object_field_register(sorcery, "transport", "websocket_write_timeout", AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR, OPT_INT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, write_timeout), 1, INT_MAX);
+       ast_sorcery_object_field_register(sorcery, "transport", "allow_reload", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, allow_reload));
 
        internal_sip_register_endpoint_formatter(&endpoint_transport_formatter);
 
index 0ff609a..dd69ff2 100644 (file)
@@ -346,6 +346,8 @@ struct sip_outbound_registration_client_state {
        unsigned int destroy:1;
        /*! \brief Non-zero if we have attempted sending a REGISTER with authentication */
        unsigned int auth_attempted:1;
+       /*! \brief The name of the transport to be used for the registration */
+       char *transport_name;
 };
 
 /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@@ -508,6 +510,7 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 {
        pj_status_t status;
        int *callback_invoked;
+       pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
        callback_invoked = ast_threadstorage_get(&register_callback_invoked, sizeof(int));
        if (!callback_invoked) {
@@ -517,6 +520,13 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 
        /* Due to the message going out the callback may now be invoked, so bump the count */
        ao2_ref(client_state, +1);
+       /*
+        * Set the transport in case transports were reloaded.
+        * When pjproject removes the extraneous error messages produced,
+        * we can check status and only set the transport and resend if there was an error
+        */
+       ast_sip_set_tpselector_from_transport_name(client_state->transport_name, &selector);
+       pjsip_regc_set_transport(client_state->client, &selector);
        status = pjsip_regc_send(client_state->client, tdata);
 
        /* If the attempt to send the message failed and the callback was not invoked we need to
@@ -966,6 +976,7 @@ 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));
@@ -1003,6 +1014,7 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
        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,
@@ -1171,25 +1183,6 @@ static int sip_outbound_registration_regc_alloc(void *data)
 
        pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
 
-       if (!ast_strlen_zero(registration->transport)) {
-               RAII_VAR(struct ast_sip_transport_state *, transport_state, ast_sip_get_transport_state(registration->transport), ao2_cleanup);
-
-               if (!transport_state) {
-                       ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' "
-                               " for outbound registration", registration->transport);
-                       return -1;
-               }
-
-               if (transport_state->transport) {
-                       selector.type = PJSIP_TPSELECTOR_TRANSPORT;
-                       selector.u.transport = transport_state->transport;
-               } else if (transport_state->factory) {
-                       selector.type = PJSIP_TPSELECTOR_LISTENER;
-                       selector.u.listener = transport_state->factory;
-               } else {
-                       return -1;
-               }
-       }
 
        ast_assert(state->client_state->client == NULL);
        if (pjsip_regc_create(ast_sip_get_pjsip_endpoint(), state->client_state,
@@ -1198,6 +1191,7 @@ static int sip_outbound_registration_regc_alloc(void *data)
                return -1;
        }
 
+       ast_sip_set_tpselector_from_transport_name(registration->transport, &selector);
        pjsip_regc_set_transport(state->client_state->client, &selector);
 
        if (!ast_strlen_zero(registration->outbound_proxy)) {
index bde7075..0da4319 100644 (file)
@@ -1559,6 +1559,28 @@ void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, co
        return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
 }
 
+/*!
+ * \internal
+ * \brief Wrapper for pjsip_evsub_send_request
+ *
+ * This function (re)sets the transport before sending to catch cases
+ * where the transport might have changed.
+ *
+ * If pjproject gives us the ability to resend, we'll only reset the transport
+ * if PJSIP_ETPNOTAVAIL is returned from send.
+ *
+ * \returns pj_status_t
+ */
+static pj_status_t internal_pjsip_evsub_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
+{
+       pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+       ast_sip_set_tpselector_from_transport_name(sub_tree->endpoint->transport, &selector);
+       pjsip_dlg_set_transport(sub_tree->dlg, &selector);
+
+       return pjsip_evsub_send_request(sub_tree->evsub, tdata);
+}
+
 /* XXX This function is not used. */
 struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
                struct ast_sip_endpoint *endpoint, const char *resource)
@@ -1606,7 +1628,7 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
        evsub = sub_tree->evsub;
 
        if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
-               pjsip_evsub_send_request(evsub, tdata);
+               internal_pjsip_evsub_send_request(sub_tree, tdata);
        } else {
                /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
                 * being called and terminating the subscription. Therefore, we don't
@@ -1687,8 +1709,8 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
 {
 #ifdef TEST_FRAMEWORK
        struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
-#endif
        pjsip_evsub *evsub = sub_tree->evsub;
+#endif
        int res;
 
        if (allocate_tdata_buffer(tdata)) {
@@ -1696,7 +1718,8 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
                return -1;
        }
 
-       res = pjsip_evsub_send_request(evsub, tdata) == PJ_SUCCESS ? 0 : -1;
+       res = internal_pjsip_evsub_send_request(sub_tree, tdata);
+
        subscription_persistence_update(sub_tree, NULL);
 
        ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
@@ -1705,7 +1728,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
                pjsip_evsub_get_state_name(evsub),
                ast_sorcery_object_get_id(endpoint));
 
-       return res;
+       return (res == PJ_SUCCESS ? 0 : -1);
 }
 
 /*!
index e7dd5b9..9836871 100644 (file)
@@ -888,10 +888,32 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
        return 0;
 }
 
+/*!
+ * \internal
+ * \brief Wrapper for pjsip_inv_send_msg
+ *
+ * This function (re)sets the transport before sending to catch cases
+ * where the transport might have changed.
+ *
+ * If pjproject gives us the ability to resend, we'll only reset the transport
+ * if PJSIP_ETPNOTAVAIL is returned from send.
+ *
+ * \returns pj_status_t
+ */
+static pj_status_t internal_pjsip_inv_send_msg(pjsip_inv_session *inv, const char *transport_name, pjsip_tx_data *tdata)
+{
+       pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+       ast_sip_set_tpselector_from_transport_name(transport_name, &selector);
+       pjsip_dlg_set_transport(inv->dlg, &selector);
+
+       return pjsip_inv_send_msg(inv, tdata);
+}
+
 void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
 {
        handle_outgoing_response(session, tdata);
-       pjsip_inv_send_msg(session->inv_session, tdata);
+       internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
        return;
 }
 
@@ -1087,7 +1109,8 @@ void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip
        }
 
        handle_outgoing_request(session, tdata);
-       pjsip_inv_send_msg(session->inv_session, tdata);
+       internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
+
        return;
 }
 
@@ -1852,7 +1875,7 @@ static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct a
                if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) != PJ_SUCCESS) {
                        pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
                }
-               pjsip_inv_send_msg(inv_session, tdata);
+               internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                return NULL;
        }
        return inv_session;
@@ -2005,7 +2028,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
                        pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
                } else {
-                       pjsip_inv_send_msg(inv_session, tdata);
+                       internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
                return;
        }
@@ -2015,7 +2038,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
                        pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
                } else {
-                       pjsip_inv_send_msg(inv_session, tdata);
+                       internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
                ao2_cleanup(invite);
        }