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.
pull/1680/head
Maximilian Fridrich 11 months ago committed by github-actions[bot]
parent 2bd999ae5f
commit d8caa664a8

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

Loading…
Cancel
Save