From: Maximilian Fridrich Date: Fri, 7 Nov 2025 11:27:11 +0000 (+0100) Subject: res_pjsip_messaging: Add support for following 3xx redirects X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d8caa664a8e1be4b9b85c7a0eac17ba498dbcc6a;p=thirdparty%2Fasterisk.git 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; }