mirror of
https://github.com/asterisk/asterisk.git
synced 2026-06-03 17:52:02 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user