res_pjsip: Symmetric transports
authorGeorge Joseph <gjoseph@digium.com>
Tue, 7 Mar 2017 14:33:26 +0000 (07:33 -0700)
committerGeorge Joseph <gjoseph@digium.com>
Thu, 16 Mar 2017 15:49:07 +0000 (09:49 -0600)
A new transport parameter 'symmetric_transport' has been added.

When a request from a dynamic contact comes in on a transport with
this option set to 'yes', the transport name will be saved and used
for subsequent outgoing requests like OPTIONS, NOTIFY and INVITE.
It's saved as a contact uri parameter named 'x-ast-txp' and will
display with the contact uri in CLI, AMI, and ARI output.  On the
outgoing request, if a transport wasn't explicitly set on the
endpoint AND the request URI is not a hostname, the saved transport
will be used and the 'x-ast-txp' parameter stripped from the
outgoing packet.

* config_transport was modified to accept and store the new parameter.

* config_transport/transport_apply was updated to store the transport
  name in the pjsip_transport->info field using the pjsip_transport->pool
  on UDP transports.

* A 'multihomed_on_rx_message' function was added to
  pjsip_message_ip_updater that, for incoming requests, retrieves the
  transport name from pjsip_transport->info and retrieves the transport.
  If transport->symmetric_transport is set, an 'x-ast-txp' uri parameter
  containing the transport name is added to the incoming Contact header.

* An 'ast_sip_get_transport_name' function was added to res_pjsip.
  It takes an ast_sip_endpoint and a pjsip_sip_uri and returns a
  transport name if endpoint->transport is set or if there's an
  'x-ast-txp' parameter on the uri and the uri host is an ipv4 or
  ipv6 address.  Otherwise it returns NULL.

* An 'ast_sip_dlg_set_transport' function was added to res_pjsip
  which takes an ast_sip_endpoint, a pjsip_dialog, and an optional
  pjsip_tpselector.  It calls ast_sip_get_transport_name() and if
  a non-NULL is returned, sets the selector and sets the transport
  on the dialog.  If a selector was passed in, it's updated.

* res_pjsip/ast_sip_create_dialog_uac and ast_sip_create_dialog_uas
  were modified to call ast_sip_dlg_set_transport() instead of their
  original logic.

* res_pjsip/create_out_of_dialog_request was modified to call
  ast_sip_get_transport_name() and pjsip_tx_data_set_transport()
  instead of its original logic.

* Existing transport logic was removed from endpt_send_request
  since that can only be called after a create_out_of_dialog_request.

* res_pjsip/ast_sip_create_rdata was converted to a wrapper around
  a new 'ast_sip_create_rdata_with_contact' function which allows
  a contact_uri to be specified in addition to the existing
  parameters.  (See below)

* res_pjsip_pubsub/internal_pjsip_evsub_send_request was eliminated
  since all it did was transport selection and that is now done in
  ast_sip_create_dialog_uac and ast_sip_create_dialog_uas.

* 'contact_uri' was added to subscription_persistence.  This was
  necessary because although the parsed rdata contact header has the
  x-ast-txp parameter added (if appropriate),
  subscription_persistence_update stores the raw packet which
  doesn't have it.  subscription_persistence_recreate was then
  updated to call ast_sip_create_rdata_with_contact with the
  persisted contact_uri so the recreated subscription has the
  correct transport info to send the NOTIFYs.

* res_pjsip_session/internal_pjsip_inv_send_msg was eliminated since
  all it did was transport selection and that is now done in
  ast_sip_create_dialog_uac.

* pjsip_message_ip_updater/multihomed_on_tx_message was updated
  to remove all traces of the x-ast-txp parameter from the
  outgoing headers.

NOTE:  This change does NOT modify the behavior of permanent
contacts specified on an aor.  To do so would require that the
permanent contact's contact uri be updated with the x-ast-txp
parameter and the aor sorcery object updated.  If we need to
persue this, we need to think about cloning permanent contacts into
the same store as the dynamic ones on an aor load so they can be
updated without disturbing the originally configured value.

You CAN add the x-ast-txp parameter to a permanent contact's uri
but it would be much simpler to just set endpoint->transport.

Change-Id: I4ee1f51473da32ca54b877cd158523efcef9655f

CHANGES
configs/samples/pjsip.conf.sample
contrib/ast-db-manage/config/versions/15db7b91a97a_add_rtcp_mux.py
contrib/ast-db-manage/config/versions/f638dbe2eb23_symmetric_transport.py [new file with mode: 0644]
include/asterisk/res_pjsip.h
res/res_pjsip.c
res/res_pjsip/config_transport.c
res/res_pjsip/pjsip_message_ip_updater.c
res/res_pjsip_pubsub.c
res/res_pjsip_session.c

diff --git a/CHANGES b/CHANGES
index 4e9df4f..e391d1e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -118,6 +118,22 @@ app_voicemail
  * Added 'fromstring' field to the voicemail boxes. If set, it will override
    the global 'fromstring' field on a per-mailbox basis.
 
+res_pjsip
+------------------
+ * A new transport parameter 'symmetric_transport' has been added.
+   When a request from a dynamic contact comes in on a transport with this
+   option set to 'yes', the transport name will be saved and used for
+   subsequent outgoing requests like OPTIONS, NOTIFY and INVITE.  It's
+   saved as a contact uri parameter named 'x-ast-txp' and will display with
+   the contact uri in CLI, AMI, and ARI output.  On the outgoing request,
+   if a transport wasn't explicitly set on the endpoint AND the request URI
+   is not a hostname, the saved transport will be used and the 'x-ast-txp'
+   parameter stripped from the outgoing packet.  To facilitate recreation of
+   subscriptions on asterisk restart, a new column 'contact_uri' needed to be
+   added to the ps_subcsription_persistence table.  Since new columns were
+   added to both transport and subscription_persistence, an alembic upgrade
+   should be run to bring the database tables up to date.
+
 res_pjsip_transport_websocket
 ------------------
  * Removed non-secure websocket support.  Firefox and Chrome have not allowed
index b18fdb2..120a7ef 100644 (file)
                     ; 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.
+;symmetric_transport=no ; When a request from a dynamic contact comes in on a
+                        ; transport with this option set to 'yes', the transport
+                        ; name will be saved and used for subsequent outgoing
+                        ; requests like OPTIONS, NOTIFY and INVITE.  It's saved
+                        ; as a contact uri parameter named 'x-ast-txp' and will
+                        ; display with the contact uri in CLI, AMI, and ARI
+                        ; output.  On the outgoing request, if a transport
+                        ; wasn't explicitly set on the endpoint AND the request
+                        ; URI is not a hostname, the saved transport will be
+                        ; used and the 'x-ast-txp' parameter stripped from the
+                        ; outgoing packet.
 
 ;==========================AOR SECTION OPTIONS=========================
 ;[aor]
index 50d3ee3..8b0214a 100644 (file)
@@ -12,6 +12,7 @@ down_revision = '465e70e8c337'
 
 from alembic import op
 import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
 
 YESNO_NAME = 'yesno_values'
 YESNO_VALUES = ['yes', 'no']
diff --git a/contrib/ast-db-manage/config/versions/f638dbe2eb23_symmetric_transport.py b/contrib/ast-db-manage/config/versions/f638dbe2eb23_symmetric_transport.py
new file mode 100644 (file)
index 0000000..51b5066
--- /dev/null
@@ -0,0 +1,32 @@
+"""symmetric_transport
+
+Revision ID: f638dbe2eb23
+Revises: 15db7b91a97a
+Create Date: 2017-03-09 09:38:59.513479
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f638dbe2eb23'
+down_revision = '15db7b91a97a'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # yesno_values have already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+    op.add_column('ps_transports', sa.Column('symmetric_transport', yesno_values))
+    op.add_column('ps_subscription_persistence', sa.Column('contact_uri', sa.String(256)))
+
+def downgrade():
+    op.drop_column('ps_subscription_persistence', 'contact_uri')
+    op.drop_column('ps_transports', 'symmetric_transport')
index fb04513..c6c308b 100644 (file)
@@ -194,6 +194,8 @@ struct ast_sip_transport {
        int write_timeout;
        /*! Allow reload */
        int allow_reload;
+       /*! Automatically send requests out the same transport requests have come in on */
+       int symmetric_transport;
 };
 
 #define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias"
@@ -765,6 +767,10 @@ struct ast_sip_endpoint {
        unsigned int asymmetric_rtp_codec;
 };
 
+/*! URI parameter for symmetric transport */
+#define AST_SIP_X_AST_TXP "x-ast-txp"
+#define AST_SIP_X_AST_TXP_LEN 9
+
 /*!
  * \brief Initialize an auth vector with the configured values.
  *
@@ -1659,6 +1665,26 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
 
 /*!
  * \brief General purpose method for creating an rdata structure using specific information
+ * \since 13.15.0
+ *
+ * \param rdata[out] The rdata structure that will be populated
+ * \param packet A SIP message
+ * \param src_name The source IP address of the message
+ * \param src_port The source port of the message
+ * \param transport_type The type of transport the message was received on
+ * \param local_name The local IP address the message was received on
+ * \param local_port The local port the message was received on
+ * \param contact_uri The contact URI of the message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_create_rdata_with_contact(pjsip_rx_data *rdata, char *packet,
+       const char *src_name, int src_port, char *transport_type, const char *local_name,
+       int local_port, const char *contact_uri);
+
+/*!
+ * \brief General purpose method for creating an rdata structure using specific information
  *
  * \param rdata[out] The rdata structure that will be populated
  * \param packet A SIP message
@@ -1671,8 +1697,8 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
  * \retval 0 success
  * \retval -1 failure
  */
-int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port, char *transport_type,
-       const char *local_name, int local_port);
+int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_name,
+       int src_port, char *transport_type, const char *local_name, int local_port);
 
 /*!
  * \brief General purpose method for creating a SIP request
@@ -2709,4 +2735,54 @@ void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
 void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period,
        unsigned int *prune_interval);
 
+/*!
+ * \brief Get the transport name from an endpoint or request uri
+ * \since 13.15.0
+ *
+ * \param endpoint
+ * \param sip_uri
+ * \param buf Buffer to receive transport name
+ * \param buf_len Buffer length
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ *
+ * \note
+ * If endpoint->transport is not NULL, it is returned in buf.
+ * Otherwise if sip_uri has an 'x-ast-txp' parameter AND the sip_uri host is
+ * an ip4 or ip6 address, its value is returned,
+ */
+int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
+       pjsip_sip_uri *sip_uri, char *buf, size_t buf_len);
+
+/*!
+ * \brief Sets pjsip_tpselector from an endpoint or uri
+ * \since 13.15.0
+ *
+ * \param endpoint If endpoint->transport is set, it's used
+ * \param sip_uri If sip_uri contains a x-ast-txp parameter, it's used
+ * \param selector The selector to be populated
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_tpselector_from_ep_or_uri(const struct ast_sip_endpoint *endpoint,
+       pjsip_sip_uri *sip_uri, pjsip_tpselector *selector);
+
+/*!
+ * \brief Set the transport on a dialog
+ * \since 13.15.0
+ *
+ * \param endpoint
+ * \param dlg
+ * \param selector (optional)
+ *
+ * \note
+ * This API calls ast_sip_get_transport_name(endpoint, dlg->target) and if the result is
+ * non-NULL, calls pjsip_dlg_set_transport.  If 'selector' is non-NULL, it is updated with
+ * the selector used.
+ */
+int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dialog *dlg,
+       pjsip_tpselector *selector);
+
 #endif /* _RES_PJSIP_H */
index f4df498..962c4be 100644 (file)
                                                in-progress calls.</para>
                                        </description>
                                </configOption>
+                               <configOption name="symmetric_transport" default="no">
+                                       <synopsis>Use the same transport for outgoing reqests as incoming ones.</synopsis>
+                                       <description>
+                                               <para>When a request from a dynamic contact
+                                                       comes in on a transport with this option set to 'yes',
+                                                       the transport name will be saved and used for subsequent
+                                                       outgoing requests like OPTIONS, NOTIFY and INVITE.  It's
+                                                       saved as a contact uri parameter named 'x-ast-txp' and will
+                                                       display with the contact uri in CLI, AMI, and ARI output.
+                                                       On the outgoing request, if a transport wasn't explicitly
+                                                       set on the endpoint AND the request URI is not a hostname,
+                                                       the saved transport will be used and the 'x-ast-txp'
+                                                       parameter stripped from the outgoing packet.
+                                               </para>
+                                       </description>
+                               </configOption>
                        </configObject>
                        <configObject name="contact">
                                <synopsis>A way of creating an aliased name to a SIP URI</synopsis>
@@ -2762,7 +2778,54 @@ pjsip_endpoint *ast_sip_get_pjsip_endpoint(void)
        return ast_pjsip_endpoint;
 }
 
-static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *user, const char *domain, const pj_str_t *target, pjsip_tpselector *selector)
+int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
+       pjsip_sip_uri *sip_uri, char *buf, size_t buf_len)
+{
+       char *host = NULL;
+       static const pj_str_t x_name = { AST_SIP_X_AST_TXP, AST_SIP_X_AST_TXP_LEN };
+       pjsip_param *x_transport;
+
+       if (!ast_strlen_zero(endpoint->transport)) {
+               ast_copy_string(buf, endpoint->transport, buf_len);
+               return 0;
+       }
+
+       x_transport = pjsip_param_find(&sip_uri->other_param, &x_name);
+       if (!x_transport) {
+               return -1;
+       }
+
+       /* Only use x_transport if the uri host is an ip (4 or 6) address */
+       host = ast_alloca(sip_uri->host.slen + 1);
+       ast_copy_pj_str(host, &sip_uri->host, sip_uri->host.slen + 1);
+       if (!ast_sockaddr_parse(NULL, host, PARSE_PORT_FORBID)) {
+               return -1;
+       }
+
+       ast_copy_pj_str(buf, &x_transport->value, buf_len);
+
+       return 0;
+}
+
+int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dialog *dlg,
+       pjsip_tpselector *selector)
+{
+       pjsip_sip_uri *uri;
+       pjsip_tpselector sel = { .type = PJSIP_TPSELECTOR_NONE, };
+
+       uri = pjsip_uri_get_uri(dlg->target);
+       if (!selector) {
+               selector = &sel;
+       }
+
+       ast_sip_set_tpselector_from_ep_or_uri(endpoint, uri, selector);
+       pjsip_dlg_set_transport(dlg, selector);
+
+       return 0;
+}
+
+static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *user,
+       const char *domain, const pj_str_t *target, pjsip_tpselector *selector)
 {
        pj_str_t tmp, local_addr;
        pjsip_uri *uri;
@@ -2892,15 +2955,16 @@ int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip
        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)
+int ast_sip_set_tpselector_from_ep_or_uri(const struct ast_sip_endpoint *endpoint,
+       pjsip_sip_uri *sip_uri, pjsip_tpselector *selector)
 {
-       const char *transport_name = endpoint->transport;
+       char transport_name[128];
 
-       if (ast_strlen_zero(transport_name)) {
+       if (ast_sip_get_transport_name(endpoint, sip_uri, transport_name, sizeof(transport_name))) {
                return 0;
        }
 
-       return ast_sip_set_tpselector_from_transport_name(endpoint->transport, selector);
+       return ast_sip_set_tpselector_from_transport_name(transport_name, selector);
 }
 
 void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t *pool, pjsip_uri *uri)
@@ -2908,8 +2972,8 @@ void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t
        pjsip_sip_uri *sip_uri;
        int i = 0;
        pjsip_param *param;
-       const pj_str_t STR_USER = { "user", 4 };
-       const pj_str_t STR_PHONE = { "phone", 5 };
+       static const pj_str_t STR_USER = { "user", 4 };
+       static const pj_str_t STR_PHONE = { "phone", 5 };
 
        if (!endpoint || !endpoint->usereqphone || (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) {
                return;
@@ -2942,7 +3006,8 @@ void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t
        pj_list_insert_before(&sip_uri->other_param, param);
 }
 
-pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint, const char *uri, const char *request_user)
+pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
+       const char *uri, const char *request_user)
 {
        char enclosed_uri[PJSIP_MAX_URL_SIZE];
        pj_str_t local_uri = { "sip:temp@temp", 13 }, remote_uri, target_uri;
@@ -2967,12 +3032,13 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
                return NULL;
        }
 
-       if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
-               pjsip_dlg_terminate(dlg);
-               return NULL;
-       }
+       /* We have to temporarily bump up the sess_count here so the dialog is not prematurely destroyed */
+       dlg->sess_count++;
+
+       ast_sip_dlg_set_transport(endpoint, dlg, &selector);
 
        if (sip_dialog_create_from(dlg->pool, &local_uri, endpoint->fromuser, endpoint->fromdomain, &remote_uri, &selector)) {
+               dlg->sess_count--;
                pjsip_dlg_terminate(dlg);
                return NULL;
        }
@@ -3008,11 +3074,6 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
        ast_sip_add_usereqphone(endpoint, dlg->pool, dlg->target);
        ast_sip_add_usereqphone(endpoint, dlg->pool, dlg->remote.info->uri);
 
-       /* We have to temporarily bump up the sess_count here so the dialog is not prematurely destroyed */
-       dlg->sess_count++;
-
-       pjsip_dlg_set_transport(dlg, &selector);
-
        if (!ast_strlen_zero(outbound_proxy)) {
                pjsip_route_hdr route_set, *route;
                static const pj_str_t ROUTE_HNAME = { "Route", 5 };
@@ -3081,10 +3142,13 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
        pjsip_transport_type_e type = rdata->tp_info.transport->key.type;
        pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
        pjsip_transport *transport;
+       pjsip_contact_hdr *contact_hdr;
 
        ast_assert(status != NULL);
 
-       if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+       contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
+       if (ast_sip_set_tpselector_from_ep_or_uri(endpoint, pjsip_uri_get_uri(contact_hdr->uri),
+               &selector)) {
                return NULL;
        }
 
@@ -3130,8 +3194,8 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
        return dlg;
 }
 
-int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port,
-       char *transport_type, const char *local_name, int local_port)
+int ast_sip_create_rdata_with_contact(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port,
+       char *transport_type, const char *local_name, int local_port, const char *contact)
 {
        pj_str_t tmp;
 
@@ -3155,6 +3219,16 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
                return -1;
        }
 
+       if (!ast_strlen_zero(contact)) {
+               pjsip_contact_hdr *contact_hdr;
+
+               contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
+               if (contact_hdr) {
+                       contact_hdr->uri = pjsip_parse_uri(rdata->tp_info.pool, (char *)contact,
+                               strlen(contact), PJSIP_PARSE_URI_AS_NAMEADDR);
+               }
+       }
+
        pj_strdup2(rdata->tp_info.pool, &rdata->msg_info.via->recvd_param, rdata->pkt_info.src_name);
        rdata->msg_info.via->rport_param = -1;
 
@@ -3166,6 +3240,13 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
        return 0;
 }
 
+int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port,
+       char *transport_type, const char *local_name, int local_port)
+{
+       return ast_sip_create_rdata_with_contact(rdata, packet, src_name, src_port, transport_type,
+               local_name, local_port, NULL);
+}
+
 /* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
 static const pjsip_method info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
 static const pjsip_method message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
@@ -3247,14 +3328,6 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
                pj_cstr(&remote_uri, uri);
        }
 
-       if (endpoint) {
-               if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
-                       ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
-                               ast_sorcery_object_get_id(endpoint));
-                       return -1;
-               }
-       }
-
        pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound request", 256, 256);
 
        if (!pool) {
@@ -3272,6 +3345,8 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
                return -1;
        }
 
+       ast_sip_set_tpselector_from_ep_or_uri(endpoint, pjsip_uri_get_uri(sip_uri), &selector);
+
        fromuser = endpoint ? (!ast_strlen_zero(endpoint->fromuser) ? endpoint->fromuser : ast_sorcery_object_get_id(endpoint)) : NULL;
        if (sip_dialog_create_from(pool, &from, fromuser,
                                endpoint ? endpoint->fromdomain : NULL, &remote_uri, &selector)) {
@@ -3291,6 +3366,8 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
                return -1;
        }
 
+       pjsip_tx_data_set_transport(*tdata, &selector);
+
        if (endpoint && !ast_strlen_zero(endpoint->contact_user)){
                pjsip_contact_hdr *contact_hdr;
                pjsip_sip_uri *contact_uri;
@@ -3332,6 +3409,8 @@ int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg,
 {
        const pjsip_method *pmethod = get_pjsip_method(method);
 
+       ast_assert(endpoint != NULL);
+
        if (!pmethod) {
                ast_log(LOG_WARNING, "Unknown method '%s'. Cannot send request\n", method);
                return -1;
@@ -3596,7 +3675,6 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
        struct send_request_wrapper *req_wrapper;
        pj_status_t ret_val;
        pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
-       pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
        if (!cb && token) {
                /* Silly.  Without a callback we cannot do anything with token. */
@@ -3621,11 +3699,6 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
        /* Add a reference to tdata.  The wrapper destructor cleans it up. */
        pjsip_tx_data_add_ref(tdata);
 
-       if (endpoint) {
-               sip_get_tpselector_from_endpoint(endpoint, &selector);
-               pjsip_tx_data_set_transport(tdata, &selector);
-       }
-
        if (timeout > 0) {
                pj_time_val timeout_timer_val = { timeout / 1000, timeout % 1000 };
 
index 60b4507..3c41f17 100644 (file)
@@ -552,13 +552,20 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
                        }
                }
 
-               if (res == PJ_SUCCESS && (transport->tos || transport->cos)) {
-                       pj_sock_t sock;
-                       pj_qos_params qos_params;
-                       sock = pjsip_udp_transport_get_socket(temp_state->state->transport);
-                       pj_sock_get_qos_params(sock, &qos_params);
-                       set_qos(transport, &qos_params);
-                       pj_sock_set_qos_params(sock, &qos_params);
+               if (res == PJ_SUCCESS) {
+                       temp_state->state->transport->info = pj_pool_alloc(temp_state->state->transport->pool,
+                               (AST_SIP_X_AST_TXP_LEN + strlen(transport_id) + 2));
+
+                       sprintf(temp_state->state->transport->info, "%s:%s", AST_SIP_X_AST_TXP, transport_id);
+
+                       if (transport->tos || transport->cos) {
+                               pj_sock_t sock;
+                               pj_qos_params qos_params;
+                               sock = pjsip_udp_transport_get_socket(temp_state->state->transport);
+                               pj_sock_get_qos_params(sock, &qos_params);
+                               set_qos(transport, &qos_params);
+                               pj_sock_set_qos_params(sock, &qos_params);
+                       }
                }
        } else if (transport->type == AST_TRANSPORT_TCP) {
                pjsip_tcp_transport_cfg cfg;
@@ -1375,6 +1382,7 @@ int ast_sip_initialize_sorcery_transport(void)
        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));
+       ast_sorcery_object_field_register(sorcery, "transport", "symmetric_transport", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, symmetric_transport));
 
        internal_sip_register_endpoint_formatter(&endpoint_transport_formatter);
 
index 7671ad0..864d898 100644 (file)
@@ -28,6 +28,7 @@
 #define MOD_DATA_RESTRICTIONS "restrictions"
 
 static pj_status_t multihomed_on_tx_message(pjsip_tx_data *tdata);
+static pj_bool_t multihomed_on_rx_message(pjsip_rx_data *rdata);
 
 /*! \brief Outgoing message modification restrictions */
 struct multihomed_message_restrictions {
@@ -41,6 +42,7 @@ static pjsip_module multihomed_module = {
        .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 1,
        .on_tx_request = multihomed_on_tx_message,
        .on_tx_response = multihomed_on_tx_message,
+       .on_rx_request = multihomed_on_rx_message,
 };
 
 /*! \brief Helper function to get (or allocate if not already present) restrictions on a message */
@@ -151,6 +153,44 @@ static int multihomed_rewrite_sdp(struct pjmedia_sdp_session *sdp)
        return 0;
 }
 
+static void 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;
+       pjsip_sip_uri *uri;
+       pjsip_fromto_hdr *fromto;
+       pjsip_contact_hdr *contact;
+       pjsip_hdr *hdr;
+
+       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);
+               }
+       }
+
+       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);
+                       }
+               } 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);
+                       }
+               }
+       }
+
+       pjsip_tx_data_invalidate_msg(tdata);
+}
+
 static pj_status_t multihomed_on_tx_message(pjsip_tx_data *tdata)
 {
        struct multihomed_message_restrictions *restrictions = ast_sip_mod_data_get(tdata->mod_data, multihomed_module.id, MOD_DATA_RESTRICTIONS);
@@ -159,6 +199,8 @@ static pj_status_t multihomed_on_tx_message(pjsip_tx_data *tdata)
        pjsip_via_hdr *via;
        pjsip_fromto_hdr *from;
 
+       sanitize_tdata(tdata);
+
        /* Use the destination information to determine what local interface this message will go out on */
        pjsip_tpmgr_fla2_param_default(&prm);
        prm.tp_type = tdata->tp_info.transport->key.type;
@@ -273,6 +315,47 @@ static pj_status_t multihomed_on_tx_message(pjsip_tx_data *tdata)
        return PJ_SUCCESS;
 }
 
+static pj_bool_t multihomed_on_rx_message(pjsip_rx_data *rdata)
+{
+       pjsip_contact_hdr *contact;
+       pjsip_sip_uri *uri;
+       const char *transport_id;
+       struct ast_sip_transport *transport;
+       pjsip_param *x_transport;
+
+       if (rdata->msg_info.msg->type != PJSIP_REQUEST_MSG) {
+               return PJ_FALSE;
+       }
+
+       contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
+       if (!(contact && contact->uri
+               && ast_begins_with(rdata->tp_info.transport->info, AST_SIP_X_AST_TXP ":"))) {
+               return PJ_FALSE;
+       }
+
+       uri = pjsip_uri_get_uri(contact->uri);
+
+       transport_id = rdata->tp_info.transport->info + AST_SIP_X_AST_TXP_LEN + 1;
+       transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_id);
+
+       if (!(transport && transport->symmetric_transport)) {
+               return PJ_FALSE;
+       }
+
+       x_transport = PJ_POOL_ALLOC_T(rdata->tp_info.pool, pjsip_param);
+       x_transport->name = pj_strdup3(rdata->tp_info.pool, AST_SIP_X_AST_TXP);
+       x_transport->value = pj_strdup3(rdata->tp_info.pool, transport_id);
+
+       pj_list_insert_before(&uri->other_param, x_transport);
+
+       ast_debug(1, "Set transport '%s' on %.*s from %.*s:%d\n", transport_id,
+               (int)rdata->msg_info.msg->line.req.method.name.slen,
+               rdata->msg_info.msg->line.req.method.name.ptr,
+               (int)uri->host.slen, uri->host.ptr, uri->port);
+
+       return PJ_FALSE;
+}
+
 void ast_res_pjsip_cleanup_message_ip_updater(void)
 {
        ast_sip_unregister_service(&multihomed_module);
index e905024..f046762 100644 (file)
                                <configOption name="expires">
                                        <synopsis>The time at which the subscription expires</synopsis>
                                </configOption>
+                               <configOption name="contact_uri">
+                                       <synopsis>The Contact URI of the dialog for the subscription</synopsis>
+                               </configOption>
                        </configObject>
                        <configObject name="resource_list">
                                <synopsis>Resource list configuration parameters.</synopsis>
@@ -376,6 +379,8 @@ struct subscription_persistence {
        char *tag;
        /*! When this subscription expires */
        struct timeval expires;
+       /*! Contact URI */
+       char contact_uri[PJSIP_MAX_URL_SIZE];
 };
 
 /*!
@@ -591,8 +596,8 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
                return;
        }
 
-       ast_debug(3, "Updating persistence for '%s->%s'\n",
-               ast_sorcery_object_get_id(sub_tree->endpoint), sub_tree->root->resource);
+       ast_debug(3, "Updating persistence for '%s->%s'\n", sub_tree->persistence->endpoint,
+               sub_tree->root->resource);
 
        dlg = sub_tree->dlg;
        sub_tree->persistence->cseq = dlg->local.cseq;
@@ -600,10 +605,14 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
        if (rdata) {
                int expires;
                pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+               pjsip_contact_hdr *contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
 
                expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
                sub_tree->persistence->expires = ast_tvadd(ast_tvnow(), ast_samp2tv(expires, 1));
 
+               pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri,
+                       sub_tree->persistence->contact_uri, sizeof(sub_tree->persistence->contact_uri));
+
                /* When receiving a packet on an streaming transport, it's possible to receive more than one SIP
                 * message at a time into the rdata->pkt_info.packet buffer. However, the rdata->msg_info.msg_buf
                 * will always point to the proper SIP message that is to be processed. When updating subscription
@@ -1550,8 +1559,9 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags)
        pj_pool_reset(pool);
        rdata.tp_info.pool = pool;
 
-       if (ast_sip_create_rdata(&rdata, persistence->packet, persistence->src_name, persistence->src_port,
-               persistence->transport_key, persistence->local_name, persistence->local_port)) {
+       if (ast_sip_create_rdata_with_contact(&rdata, persistence->packet, persistence->src_name,
+               persistence->src_port, persistence->transport_key, persistence->local_name,
+               persistence->local_port, persistence->contact_uri)) {
                ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The message could not be parsed\n",
                        persistence->endpoint);
                ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
@@ -1703,28 +1713,6 @@ 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)
@@ -1772,7 +1760,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) {
-               internal_pjsip_evsub_send_request(sub_tree, tdata);
+               pjsip_evsub_send_request(sub_tree->evsub, tdata);
        } else {
                /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
                 * being called and terminating the subscription. Therefore, we don't
@@ -1869,7 +1857,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
                return -1;
        }
 
-       res = internal_pjsip_evsub_send_request(sub_tree, tdata);
+       res = pjsip_evsub_send_request(sub_tree->evsub, tdata);
 
        subscription_persistence_update(sub_tree, NULL, SUBSCRIPTION_PERSISTENCE_SEND_REQUEST);
 
@@ -5283,6 +5271,8 @@ static int load_module(void)
                persistence_tag_str2struct, persistence_tag_struct2str, NULL, 0, 0);
        ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
                persistence_expires_str2struct, persistence_expires_struct2str, NULL, 0, 0);
+       ast_sorcery_object_field_register(sorcery, "subscription_persistence", "contact_uri", "", OPT_CHAR_ARRAY_T, 0,
+               CHARFLDSET(struct subscription_persistence, contact_uri));
 
        if (apply_list_configuration(sorcery)) {
                ast_sip_unregister_service(&pubsub_module);
index ad9670c..fec0768 100644 (file)
@@ -973,32 +973,10 @@ 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);
-       internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
+       pjsip_inv_send_msg(session->inv_session, tdata);
        return;
 }
 
@@ -1229,7 +1207,7 @@ void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip
                             MOD_DATA_ON_RESPONSE, on_response);
 
        handle_outgoing_request(session, tdata);
-       internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
+       pjsip_inv_send_msg(session->inv_session, tdata);
 
        return;
 }
@@ -2051,7 +2029,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);
                }
-               internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+               pjsip_inv_send_msg(inv_session, tdata);
                return NULL;
        }
        return inv_session;
@@ -2222,7 +2200,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 {
-                               internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+                               pjsip_inv_send_msg(inv_session, tdata);
                        }
                }
                return;
@@ -2234,7 +2212,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 {
-                       internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+                       pjsip_inv_send_msg(inv_session, tdata);
                }
 #ifdef HAVE_PJSIP_INV_SESSION_REF
                pjsip_inv_dec_ref(inv_session);
@@ -2247,7 +2225,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 {
-                       internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+                       pjsip_inv_send_msg(inv_session, tdata);
                }
 #ifdef HAVE_PJSIP_INV_SESSION_REF
                pjsip_inv_dec_ref(inv_session);