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);