]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_pjsip_messaging: Add support for following 3xx redirects
authorMaximilian Fridrich <m.fridrich@commend.com>
Fri, 7 Nov 2025 11:27:11 +0000 (12:27 +0100)
committergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 30 Dec 2025 15:09:34 +0000 (15:09 +0000)
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.

res/res_pjsip_messaging.c

index a00e481d1ab40ce67d27517991b2399c91fc2746..f9ab8717c6405992d239293ea5b60e42809838a6 100644 (file)
 #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;
        }