res_pjsip/config_transport: Allow reloading transports.
authorGeorge Joseph <george.joseph@fairview5.com>
Thu, 11 Feb 2016 17:01:05 +0000 (10:01 -0700)
committerGeorge Joseph <george.joseph@fairview5.com>
Sat, 20 Feb 2016 00:57:55 +0000 (18:57 -0600)
The 'reload' mechanism actually involves closing the underlying
socket and calling the appropriate udp, tcp or tls start functions
again.  Only outbound_registration, pubsub and session needed work
to reset the transport before sending requests to insure that the
pjsip transport didn't get pulled out from under them.

In my testing, no calls were dropped when a transport was changed
for any of the 3 transport types even if ip addresses or ports were
changed. To be on the safe side however, a new transport option was
added (allow_reload) which defaults to 'no'.  Unless it's explicitly
set to 'yes' for a transport, changes to that transport will be ignored
on a reload of res_pjsip.  This should preserve the current behavior.

Change-Id: I5e759850e25958117d4c02f62ceb7244d7ec9edf

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 5e4c428..260aa2f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -231,6 +231,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);
        }