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.
This commit is contained in:
Maximilian Fridrich
2025-11-07 12:27:11 +01:00
parent 569b3ed995
commit ebde5ae5cb

View File

@@ -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;
}