Commit 8cd58fd39d for asterisk.org
commit 8cd58fd39d7871b16cac3fec72a142fa984b4254
Author: Mehrdad Seifzadeh <mehrdad.seifzadeh@gmail.com>
Date: Fri Jun 5 11:14:30 2026 +0330
chan_pjsip: Store transport info for outgoing channels
Outgoing PJSIP channels did not have transport information stored in
their session datastore when CHANNEL(pjsip,local_addr) or
CHANNEL(pjsip,remote_addr) was read. As a result, the address fields
were empty on B-leg channels even though other dialog fields such as
call-id and URIs were available.
Store the selected local transport address and destination address for
outgoing UAC session requests once the request is transmitted and the
transport information is known.
Fixes: #871
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 008f41206b..c0e7611beb 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -271,6 +271,102 @@ static struct ast_datastore_info transport_info = {
.destroy = transport_info_destroy,
};
+
+/*!
+ * \brief Determine the local signaling address used for an outgoing request.
+ *
+ * For unreliable transports, the transport local address may be a wildcard
+ * address. Ask the PJSIP transport manager for the local interface selected
+ * for the request destination, then fall back to the transport local address
+ * if needed.
+ */
+static int set_transport_info_local_addr(struct pjsip_tx_data *tdata, pj_sockaddr *local_addr)
+{
+ if (!(tdata->tp_info.transport->flag & PJSIP_TRANSPORT_RELIABLE)) {
+ pjsip_tpmgr_fla2_param prm;
+
+ pjsip_tpmgr_fla2_param_default(&prm);
+ prm.tp_type = tdata->tp_info.transport->key.type;
+ pj_strset2(&prm.dst_host, tdata->tp_info.dst_name);
+ prm.local_if = PJ_TRUE;
+
+ if (pjsip_tpmgr_find_local_addr2(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()),
+ tdata->pool, &prm) == PJ_SUCCESS) {
+ int af = prm.tp_type & PJSIP_TRANSPORT_IPV6 ? pj_AF_INET6() : pj_AF_INET();
+
+ if (prm.tp_type == PJSIP_TRANSPORT_UDP || prm.tp_type == PJSIP_TRANSPORT_UDP6) {
+ prm.ret_port = tdata->tp_info.transport->local_name.port;
+ }
+
+ if (pj_sockaddr_init(af, local_addr, &prm.ret_addr, prm.ret_port) == PJ_SUCCESS) {
+ return 0;
+ }
+ }
+ }
+
+ pj_sockaddr_cp(local_addr, &tdata->tp_info.transport->local_addr);
+ return pj_sockaddr_has_addr(local_addr) ? 0 : -1;
+}
+
+/*!
+ * \brief Store transport information for outgoing PJSIP session requests.
+ *
+ * Incoming PJSIP channels get their transport information from the received
+ * request. Outgoing channels may not have this information available through
+ * the channel datastore, so store the selected transport addresses once the
+ * outgoing request has been created and a destination is known.
+ */
+static pj_status_t transport_info_on_tx_request(pjsip_tx_data *tdata)
+{
+ RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ struct transport_info_data *transport_data;
+ pjsip_dialog *dlg;
+
+ if (!tdata->tp_info.transport
+ || !pj_sockaddr_has_addr(&tdata->tp_info.dst_addr)) {
+ return PJ_SUCCESS;
+ }
+
+ dlg = pjsip_tdata_get_dlg(tdata);
+ if (!dlg) {
+ return PJ_SUCCESS;
+ }
+
+ session = ast_sip_dialog_get_session(dlg);
+ if (!session
+ || !session->inv_session
+ || session->inv_session->role != PJSIP_ROLE_UAC) {
+ return PJ_SUCCESS;
+ }
+
+ datastore = ast_sip_session_get_datastore(session, "transport_info");
+ if (datastore) {
+ return PJ_SUCCESS;
+ }
+
+ datastore = ast_sip_session_alloc_datastore(&transport_info, "transport_info");
+ if (!datastore) {
+ return PJ_SUCCESS;
+ }
+
+ transport_data = ast_calloc(1, sizeof(*transport_data));
+ if (!transport_data) {
+ return PJ_SUCCESS;
+ }
+
+ if (set_transport_info_local_addr(tdata, &transport_data->local_addr)) {
+ ast_free(transport_data);
+ return PJ_SUCCESS;
+ }
+ pj_sockaddr_cp(&transport_data->remote_addr, &tdata->tp_info.dst_addr);
+
+ datastore->data = transport_data;
+ ast_sip_session_add_datastore(session, datastore);
+
+ return PJ_SUCCESS;
+}
+
static struct ast_datastore_info direct_media_mitigation_info = { };
static int direct_media_mitigate_glare(struct ast_sip_session *session)
@@ -1956,10 +2052,11 @@ static void transfer_redirect(struct ast_sip_session *session, const char *targe
ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
}
-/*! \brief REFER Callback module, used to attach session data structure to subscription */
-static pjsip_module refer_callback_module = {
- .name = { "REFER Callback", 14 },
+/*! \brief General PJSIP module for chan_pjsip, currently used for REFER subscription data and outgoing request callbacks */
+static pjsip_module chan_pjsip_module = {
+ .name = { "chan_pjsip", 10 },
.id = -1,
+ .on_tx_request = transport_info_on_tx_request,
};
/*!
@@ -1979,7 +2076,7 @@ static void xfer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
return;
}
- chan = pjsip_evsub_get_mod_data(sub, refer_callback_module.id);
+ chan = pjsip_evsub_get_mod_data(sub, chan_pjsip_module.id);
if (!chan) {
return;
}
@@ -2007,7 +2104,7 @@ static void xfer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
/* Since no subscription is desired, assume that call has been transferred successfully. */
/* Channel reference will be released at end of function */
/* Terminate subscription. */
- pjsip_evsub_set_mod_data(sub, refer_callback_module.id, NULL);
+ pjsip_evsub_set_mod_data(sub, chan_pjsip_module.id, NULL);
pjsip_evsub_terminate(sub, PJ_TRUE);
res = -1;
}
@@ -2071,7 +2168,7 @@ static void xfer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
}
}
/* Finished. Remove session from subscription */
- pjsip_evsub_set_mod_data(sub, refer_callback_module.id, NULL);
+ pjsip_evsub_set_mod_data(sub, chan_pjsip_module.id, NULL);
ast_debug(3, "Transfer channel %s completed: %d %.*s (%s)\n",
ast_channel_name(chan),
status_line.code,
@@ -2107,10 +2204,10 @@ static void transfer_refer(struct ast_sip_session *session, const char *target)
return;
}
- /* refer_callback_module requires a reference to chan
+ /* chan_pjsip_module requires a reference to chan
* which will be released in xfer_client_on_evsub_state()
* when the implicit REFER subscription terminates */
- pjsip_evsub_set_mod_data(sub, refer_callback_module.id, chan);
+ pjsip_evsub_set_mod_data(sub, chan_pjsip_module.id, chan);
ao2_ref(chan, +1);
if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, target), &packet) != PJ_SUCCESS) {
@@ -2132,7 +2229,7 @@ static void transfer_refer(struct ast_sip_session *session, const char *target)
failure:
message = AST_TRANSFER_FAILED;
ast_queue_control_data(chan, AST_CONTROL_TRANSFER, &message, sizeof(message));
- pjsip_evsub_set_mod_data(sub, refer_callback_module.id, NULL);
+ pjsip_evsub_set_mod_data(sub, chan_pjsip_module.id, NULL);
pjsip_evsub_terminate(sub, PJ_FALSE);
ao2_ref(chan, -1);
@@ -3493,7 +3590,7 @@ static int load_module(void)
ast_manager_register_xml(app_pjsip_hangup, EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, pjsip_action_hangup);
- ast_sip_register_service(&refer_callback_module);
+ ast_sip_register_service(&chan_pjsip_module);
ast_sip_session_register_supplement(&chan_pjsip_supplement);
ast_sip_session_register_supplement(&chan_pjsip_supplement_response);
@@ -3533,7 +3630,7 @@ end:
ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response);
ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
ast_sip_session_unregister_supplement(&call_pickup_supplement);
- ast_sip_unregister_service(&refer_callback_module);
+ ast_sip_unregister_service(&chan_pjsip_module);
ast_custom_function_unregister(&dtmf_mode_function);
ast_custom_function_unregister(&moh_passthrough_function);
ast_custom_function_unregister(&media_offer_function);
@@ -3566,7 +3663,7 @@ static int unload_module(void)
ast_sip_session_unregister_supplement(&chan_pjsip_prack_supplement);
ast_sip_session_unregister_supplement(&call_pickup_supplement);
- ast_sip_unregister_service(&refer_callback_module);
+ ast_sip_unregister_service(&chan_pjsip_module);
ast_custom_function_unregister(&dtmf_mode_function);
ast_custom_function_unregister(&moh_passthrough_function);