Commit d8caa664a8 for asterisk.org
commit d8caa664a8e1be4b9b85c7a0eac17ba498dbcc6a
Author: Maximilian Fridrich <m.fridrich@commend.com>
Date: Fri Nov 7 12:27:11 2025 +0100
res_pjsip_messaging: Add support for following 3xx redirects
This commit integrates the redirect module into res_pjsip_messaging
to enable following 3xx redirect responses for outgoing SIP MESSAGEs.
When follow_redirect_methods contains 'message' on an endpoint, Asterisk
will now follow 3xx redirect responses for MESSAGEs, similar to how
it behaves for INVITE responses.
Resolves: #1576
UserNote: A new pjsip endpoint option follow_redirect_methods was added.
This option is a comma-delimited, case-insensitive list of SIP methods
for which SIP 3XX redirect responses are followed. An alembic upgrade
script has been added for adding this new option to the Asterisk
database.
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index a00e481d1a..f9ab8717c6 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -126,6 +126,7 @@
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_redirect.h"
#include "asterisk/res_pjsip_session.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/test.h"
@@ -580,6 +581,69 @@ static struct msg_data *msg_data_create(const struct ast_msg *msg, const char *d
return mdata;
}
+/*!
+ * \internal
+ * \brief Callback data for MESSAGE response handling
+ */
+struct message_response_data {
+ char *from;
+ char *to;
+ char *body;
+ char *content_type;
+ struct ast_sip_redirect_state *redirect_state;
+};
+
+static void message_response_data_destroy(void *obj)
+{
+ struct message_response_data *resp_data = obj;
+
+ ast_free(resp_data->from);
+ ast_free(resp_data->to);
+ ast_free(resp_data->body);
+ ast_free(resp_data->content_type);
+
+ if (resp_data->redirect_state) {
+ ast_sip_redirect_state_destroy(resp_data->redirect_state);
+ }
+}
+
+static struct message_response_data *message_response_data_create(
+ struct ast_sip_endpoint *endpoint,
+ const char *from,
+ const char *to,
+ const char *body,
+ const char *content_type,
+ const char *initial_uri)
+{
+ struct message_response_data *resp_data;
+
+ resp_data = ao2_alloc_options(sizeof(*resp_data), message_response_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!resp_data) {
+ return NULL;
+ }
+
+ resp_data->from = ast_strdup(from);
+ resp_data->to = ast_strdup(to);
+ resp_data->body = ast_strdup(body);
+ resp_data->content_type = ast_strdup(content_type);
+
+ if (!resp_data->from || !resp_data->to || !resp_data->body || !resp_data->content_type) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for response data strings on endpoint '%s'.\n",
+ ast_sorcery_object_get_id(endpoint));
+ ao2_ref(resp_data, -1);
+ return NULL;
+ }
+
+ resp_data->redirect_state = ast_sip_redirect_state_create(endpoint, initial_uri);
+ if (!resp_data->redirect_state) {
+ ast_log(LOG_ERROR, "Failed to create redirect state for endpoint '%s'.\n", ast_sorcery_object_get_id(endpoint));
+ ao2_ref(resp_data, -1);
+ return NULL;
+ }
+
+ return resp_data;
+}
+
static void update_content_type(pjsip_tx_data *tdata, struct ast_msg *msg, struct ast_sip_body *body)
{
static const pj_str_t CONTENT_TYPE = { "Content-Type", sizeof("Content-Type") - 1 };
@@ -609,6 +673,212 @@ static void update_content_type(pjsip_tx_data *tdata, struct ast_msg *msg, struc
}
}
+/* Forward declaration for callback */
+static void msg_response_callback(void *token, pjsip_event *e);
+
+/*!
+ * \internal
+ * \brief Send a MESSAGE to a redirect target
+ *
+ * \param resp_data Response data containing redirect state and message info
+ * \param target_uri The URI to send the message to
+ *
+ * \return 0: success, -1: failure
+ */
+static int send_message_to_uri(struct message_response_data *resp_data, const char *target_uri)
+{
+ pjsip_tx_data *tdata;
+ struct message_response_data *new_resp_data;
+ struct ast_sip_endpoint *endpoint;
+ struct ast_sip_body body = {
+ .type = "text",
+ .subtype = "plain",
+ .body_text = resp_data->body
+ };
+
+ if (!resp_data->redirect_state) {
+ ast_log(LOG_ERROR, "No redirect state available for sending a redirect message.\n");
+ return -1;
+ }
+
+ endpoint = ast_sip_redirect_get_endpoint(resp_data->redirect_state);
+
+ ast_debug(1, "Sending redirected MESSAGE to '%s' (hop %d) on endpoint '%s'\n",
+ target_uri, ast_sip_redirect_get_hop_count(resp_data->redirect_state), ast_sorcery_object_get_id(endpoint));
+
+ if (ast_sip_create_request("MESSAGE", NULL, endpoint, target_uri, NULL, &tdata)) {
+ ast_log(LOG_WARNING, "Could not create redirect request for endpoint '%s'.\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ /* Update To header if we have one */
+ if (!ast_strlen_zero(resp_data->to)) {
+ ast_sip_update_to_uri(tdata, resp_data->to);
+ }
+
+ /* Update From header if we have one */
+ if (!ast_strlen_zero(resp_data->from)) {
+ ast_sip_update_from(tdata, resp_data->from);
+ }
+
+ /* Parse and set content type if provided */
+ if (!ast_strlen_zero(resp_data->content_type)) {
+ char *type_copy = ast_strdupa(resp_data->content_type);
+ char *subtype = strchr(type_copy, '/');
+ if (subtype) {
+ *subtype = '\0';
+ subtype++;
+ body.type = type_copy;
+ body.subtype = subtype;
+ }
+ }
+ ast_sip_add_body(tdata, &body);
+ if (!tdata->msg->body) {
+ pjsip_tx_data_dec_ref(tdata);
+ ast_log(LOG_ERROR, "Could not add body to redirect request on endpoint '%s'.\n", ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ /* Create new callback data - the redirect state is passed along */
+ new_resp_data = ao2_alloc(sizeof(*new_resp_data), message_response_data_destroy);
+ if (!new_resp_data) {
+ pjsip_tx_data_dec_ref(tdata);
+ ast_log(LOG_ERROR, "Could not allocate redirect callback data for endpoint '%s'.\n", ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ /* Copy message-specific data */
+ new_resp_data->from = ast_strdup(resp_data->from);
+ new_resp_data->to = ast_strdup(resp_data->to);
+ new_resp_data->body = ast_strdup(resp_data->body);
+ new_resp_data->content_type = ast_strdup(resp_data->content_type);
+
+ /* Check for allocation failures */
+ if (!new_resp_data->from || !new_resp_data->to || !new_resp_data->body || !new_resp_data->content_type) {
+ pjsip_tx_data_dec_ref(tdata);
+ ao2_ref(new_resp_data, -1);
+ ast_log(LOG_ERROR, "Failed to allocate memory for redirect callback strings for endpoint '%s'.\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ /* Transfer the redirect state to the new response data */
+ new_resp_data->redirect_state = resp_data->redirect_state;
+ resp_data->redirect_state = NULL;
+
+ /* Send with callback for potential further redirects */
+ if (ast_sip_send_request(tdata, NULL, endpoint, new_resp_data, msg_response_callback)) {
+ ao2_ref(new_resp_data, -1);
+ ast_log(LOG_ERROR, "Failed to send redirect request to '%s' on endpoint '%s'.\n",
+ new_resp_data->to, ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Handle a 3xx redirect response to a MESSAGE
+ *
+ * \param resp_data Response callback data
+ * \param rdata The redirect response data
+ */
+static void handle_message_redirect(struct message_response_data *resp_data, pjsip_rx_data *rdata)
+{
+ char *uri = NULL;
+ int hop_count;
+
+ if (!resp_data->redirect_state) {
+ ast_log(LOG_ERROR, "MESSAGE redirect: no redirect state available\n");
+ return;
+ }
+
+ /* Parse the redirect response and extract contacts */
+ if (ast_sip_redirect_parse_3xx(rdata, resp_data->redirect_state)) {
+ ast_debug(1, "MESSAGE redirect on endpoint '%s': not following redirect (parse failed or conditions not met)\n",
+ ast_sorcery_object_get_id(ast_sip_redirect_get_endpoint(resp_data->redirect_state)));
+ return;
+ }
+
+ /* Get the first URI to try */
+ if (ast_sip_redirect_get_next_uri(resp_data->redirect_state, &uri)) {
+ ast_log(LOG_WARNING, "MESSAGE redirect on endpoint '%s': no valid URIs to try\n",
+ ast_sorcery_object_get_id(ast_sip_redirect_get_endpoint(resp_data->redirect_state)));
+ return;
+ }
+
+ hop_count = ast_sip_redirect_get_hop_count(resp_data->redirect_state);
+ ast_log(LOG_NOTICE, "MESSAGE redirect on endpoint '%s': Following redirect to '%s' (hop %d/%d)\n",
+ ast_sorcery_object_get_id(ast_sip_redirect_get_endpoint(resp_data->redirect_state)), uri, hop_count, AST_SIP_MAX_REDIRECT_HOPS);
+
+ /* Try the first contact */
+ send_message_to_uri(resp_data, uri);
+ ast_free(uri);
+}
+
+/*!
+ * \internal
+ * \brief Callback for MESSAGE responses
+ *
+ * \param token Callback data
+ * \param e The pjsip event
+ */
+static void msg_response_callback(void *token, pjsip_event *e)
+{
+ struct message_response_data *resp_data = token;
+ struct ast_sip_redirect_state *state = resp_data->redirect_state;
+ struct ast_sip_endpoint *endpoint = ast_sip_redirect_get_endpoint(state);
+ pjsip_rx_data *rdata;
+ int status_code;
+ char *next_uri = NULL;
+
+ /* Check event type */
+ if (e->body.tsx_state.type == PJSIP_EVENT_TIMER) {
+ ast_debug(1, "MESSAGE request on endpoint '%s' timed out\n", ast_sorcery_object_get_id(endpoint));
+ /* Try next pending contact if available */
+ if (state && !ast_sip_redirect_get_next_uri(state, &next_uri)) {
+ ast_log(LOG_NOTICE, "MESSAGE timed out on endpoint '%s', trying next Contact: '%s'\n",
+ ast_sorcery_object_get_id(endpoint), next_uri);
+ send_message_to_uri(resp_data, next_uri);
+ ast_free(next_uri);
+ }
+ ao2_ref(resp_data, -1);
+ return;
+ }
+
+ if (e->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
+ ast_debug(3, "MESSAGE response event type %d (not RX_MSG) on endpoint '%s'.\n",
+ e->body.tsx_state.type, ast_sorcery_object_get_id(endpoint));
+ ao2_ref(resp_data, -1);
+ return;
+ }
+
+ rdata = e->body.tsx_state.src.rdata;
+ status_code = e->body.tsx_state.tsx->status_code;
+
+ ast_debug(3, "Received MESSAGE response %d on endpoint '%s'.\n", status_code, ast_sorcery_object_get_id(endpoint));
+
+ /* Handle 3xx redirects */
+ if (PJSIP_IS_STATUS_IN_CLASS(status_code, 300)) {
+ handle_message_redirect(resp_data, rdata);
+ }
+ /* If non-2xx response and we have pending contacts, try the next one */
+ else if (status_code >= 400 && state && !ast_sip_redirect_get_next_uri(state, &next_uri)) {
+ ast_log(LOG_NOTICE, "MESSAGE to redirect target failed (%d) on endpoint '%s', trying next Contact: '%s'\n",
+ status_code, ast_sorcery_object_get_id(endpoint), next_uri);
+ send_message_to_uri(resp_data, next_uri);
+ ast_free(next_uri);
+ }
+ /* Success (2xx) - don't try other contacts */
+ else if (PJSIP_IS_STATUS_IN_CLASS(status_code, 200)) {
+ ast_debug(1, "MESSAGE successfully delivered (%d) for endpoint '%s'.\n", status_code, ast_sorcery_object_get_id(endpoint));
+ }
+
+ ao2_ref(resp_data, -1);
+}
+
/*!
* \internal
* \brief Send a MESSAGE
@@ -631,6 +901,10 @@ static void update_content_type(pjsip_tx_data *tdata, struct ast_msg *msg, struc
static int msg_send(void *data)
{
struct msg_data *mdata = data; /* The caller holds a reference */
+ /* Callback data for redirect handling */
+ struct message_response_data *resp_data;
+ const char *from_uri;
+ const char *to_uri;
struct ast_sip_body body = {
.type = "text",
@@ -641,6 +915,7 @@ static int msg_send(void *data)
pjsip_tx_data *tdata;
RAII_VAR(char *, uri, NULL, ast_free);
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_str *, content_type_buf , ast_str_create(128), ast_free);
ast_debug(3, "mdata From: %s msg From: %s mdata Destination: %s msg To: %s\n",
mdata->from, ast_msg_get_from(mdata->msg), mdata->destination, ast_msg_get_to(mdata->msg));
@@ -736,7 +1011,8 @@ static int msg_send(void *data)
update_content_type(tdata, mdata->msg, &body);
- if (ast_sip_add_body(tdata, &body)) {
+ ast_sip_add_body(tdata, &body);
+ if (!tdata->msg->body) {
pjsip_tx_data_dec_ref(tdata);
ast_log(LOG_ERROR, "PJSIP MESSAGE - Could not add body to request\n");
return -1;
@@ -751,8 +1027,40 @@ static int msg_send(void *data)
ast_debug(1, "Sending message to '%s' (via endpoint %s) from '%s'\n",
uri, ast_sorcery_object_get_id(endpoint), mdata->from);
- if (ast_sip_send_request(tdata, NULL, endpoint, NULL, NULL)) {
- ast_log(LOG_ERROR, "PJSIP MESSAGE - Could not send request\n");
+ /* Determine From URI */
+ if (!ast_strlen_zero(mdata->from)) {
+ from_uri = mdata->from;
+ } else if (!ast_strlen_zero(ast_msg_get_from(mdata->msg))) {
+ from_uri = ast_msg_get_from(mdata->msg);
+ } else {
+ from_uri = "";
+ }
+
+ /* Determine To URI */
+ if (!ast_strlen_zero(ast_msg_get_to(mdata->msg))) {
+ to_uri = ast_msg_get_to(mdata->msg);
+ } else {
+ to_uri = "";
+ }
+
+ /* Build content type string */
+ ast_str_set(&content_type_buf, 0, "%s/%s", body.type, body.subtype);
+
+ /* Create callback data */
+ resp_data = message_response_data_create(endpoint, from_uri, to_uri,
+ body.body_text, ast_str_buffer(content_type_buf), uri);
+
+ if (!resp_data) {
+ pjsip_tx_data_dec_ref(tdata);
+ ast_log(LOG_ERROR, "PJSIP MESSAGE - Could not allocate callback data for endpoint '%s'\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ /* Send with callback for redirect handling */
+ if (ast_sip_send_request(tdata, NULL, endpoint, resp_data, msg_response_callback)) {
+ ao2_ref(resp_data, -1);
+ ast_log(LOG_ERROR, "PJSIP MESSAGE - Could not send request on endpoint '%s'\n", ast_sorcery_object_get_id(endpoint));
return -1;
}