mirror of
https://github.com/asterisk/asterisk.git
synced 2026-07-02 04:53:32 -07:00
res_pjsip: Introduce redirect module for handling 3xx responses
This commit introduces a new redirect handling module that provides infrastructure for following SIP 3xx redirect responses. The redirect functionality respects the endpoint's redirect_method setting and only follows redirects when set to 'uri_pjsip'. This infrastructure can be used by any PJSIP module that needs to handle 3xx redirect responses.
This commit is contained in:
@@ -703,6 +703,9 @@
|
||||
; (default: "username,ip")
|
||||
;redirect_method=user ; How redirects received from an endpoint are handled
|
||||
; (default: "user")
|
||||
;follow_redirect_methods= ; A comma-separated list of SIP methods for which redirects are followed.
|
||||
; Currently, only "message" is supported.
|
||||
; (default: "")
|
||||
;mailboxes= ; NOTIFY the endpoint when state changes for any of the specified mailboxes.
|
||||
; Asterisk will send unsolicited MWI NOTIFY messages to the endpoint when state
|
||||
; changes happen for any of the specified mailboxes. (default: "")
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"""add follow_redirect_methods to ps_endpoints
|
||||
|
||||
Revision ID: bb6d54e22913
|
||||
Revises: dc7c357dc178
|
||||
Create Date: 2025-12-12 11:26:36.591932
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bb6d54e22913'
|
||||
down_revision = 'dc7c357dc178'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('ps_endpoints', sa.Column('follow_redirect_methods', sa.String(95)))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('ps_endpoints', 'follow_redirect_methods')
|
||||
@@ -750,6 +750,16 @@ enum ast_sip_session_redirect {
|
||||
AST_SIP_REDIRECT_URI_PJSIP,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief SIP methods that are allowed to follow 3xx redirects.
|
||||
*
|
||||
* Used as bit flags in follow_redirect_methods field.
|
||||
*/
|
||||
enum ast_sip_redirect_method {
|
||||
/*! Allow MESSAGE method to follow redirects */
|
||||
AST_SIP_REDIRECT_METHOD_MESSAGE = (1 << 0),
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Incoming/Outgoing call offer/answer joint codec preference.
|
||||
*
|
||||
@@ -1114,6 +1124,8 @@ struct ast_sip_endpoint {
|
||||
unsigned int allowtransfer;
|
||||
/*! Method used when handling redirects */
|
||||
enum ast_sip_session_redirect redirect_method;
|
||||
/*! SIP methods allowed to follow 3xx redirects */
|
||||
struct ast_flags follow_redirect_methods;
|
||||
/*! Variables set on channel creation */
|
||||
struct ast_variable *channel_vars;
|
||||
/*! Whether to place a 'user=phone' parameter into the request URI if user is a number */
|
||||
@@ -4392,4 +4404,15 @@ const int ast_sip_hangup_sip2cause(int cause);
|
||||
*/
|
||||
int ast_sip_str2rc(const char *name);
|
||||
|
||||
/*!
|
||||
* \brief Parses a string representing a q_value to a float.
|
||||
*
|
||||
* Valid q values must be in the range from 0.0 to 1.0 inclusively.
|
||||
*
|
||||
* \param q_value String representing a floating point value
|
||||
*
|
||||
* \retval The parsed qvalue or -1.0 on failure.
|
||||
*/
|
||||
float ast_sip_parse_qvalue(const char *q_value);
|
||||
|
||||
#endif /* _RES_PJSIP_H */
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2025, Commend International
|
||||
*
|
||||
* Maximilian Fridrich <m.fridrich@commend.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef _RES_PJSIP_REDIRECT_H
|
||||
#define _RES_PJSIP_REDIRECT_H
|
||||
|
||||
#include <pjsip.h>
|
||||
|
||||
/*!
|
||||
* \brief Maximum number of redirect hops allowed
|
||||
*/
|
||||
#define AST_SIP_MAX_REDIRECT_HOPS 5
|
||||
|
||||
/*!
|
||||
* \brief Maximum number of redirect contacts to process
|
||||
*/
|
||||
#define AST_SIP_MAX_REDIRECT_CONTACTS 20
|
||||
|
||||
/*!
|
||||
* \brief Opaque structure for redirect state
|
||||
*
|
||||
* This structure encapsulates all state needed for handling
|
||||
* SIP 3xx redirects, including visited URIs for loop detection,
|
||||
* pending contacts for retry logic, and hop counting.
|
||||
*/
|
||||
struct ast_sip_redirect_state;
|
||||
|
||||
/*!
|
||||
* \brief Create a new redirect state
|
||||
*
|
||||
* \param endpoint The SIP endpoint
|
||||
* \param initial_uri The initial URI being contacted (for loop detection)
|
||||
*
|
||||
* \retval NULL on failure
|
||||
* \retval A new redirect state on success
|
||||
*
|
||||
* \note The caller must call ast_sip_redirect_state_destroy() when done
|
||||
*/
|
||||
struct ast_sip_redirect_state *ast_sip_redirect_state_create(
|
||||
struct ast_sip_endpoint *endpoint,
|
||||
const char *initial_uri);
|
||||
|
||||
/*!
|
||||
* \brief Check if redirect should be followed based on endpoint configuration
|
||||
*
|
||||
* \param endpoint The SIP endpoint
|
||||
* \param rdata The redirect response data containing the 3xx response
|
||||
*
|
||||
* \retval 0 if redirect should not be followed
|
||||
* \retval 1 if redirect should be followed
|
||||
*
|
||||
* \note This checks if the status code is 3xx and if the SIP method
|
||||
* (extracted from the CSeq header) is allowed to follow redirects
|
||||
* based on the endpoint's follow_redirect_methods configuration
|
||||
*/
|
||||
int ast_sip_should_redirect(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
|
||||
|
||||
/*!
|
||||
* \brief Parse a 3xx redirect response and extract contacts
|
||||
*
|
||||
* This function parses all Contact headers from a 3xx response,
|
||||
* extracts q-values, sorts contacts by priority (highest q-value first),
|
||||
* and filters out URIs that would create loops.
|
||||
*
|
||||
* \param rdata The redirect response data
|
||||
* \param state The redirect state
|
||||
*
|
||||
* \retval -1 on failure (hop limit reached, no valid contacts, etc.)
|
||||
* \retval 0 on success (at least one valid contact available)
|
||||
*
|
||||
* \note After calling this, use ast_sip_redirect_get_next_uri() to retrieve URIs
|
||||
*/
|
||||
int ast_sip_redirect_parse_3xx(pjsip_rx_data *rdata, struct ast_sip_redirect_state *state);
|
||||
|
||||
/*!
|
||||
* \brief Get the next redirect URI to try
|
||||
*
|
||||
* This function returns the next contact URI from the redirect response,
|
||||
* ordered by q-value (highest first). It also marks the URI as visited
|
||||
* to prevent loops on subsequent redirects.
|
||||
*
|
||||
* \param state The redirect state
|
||||
* \param uri_out Pointer to store the URI string (caller must free)
|
||||
*
|
||||
* \retval -1 if no more URIs available
|
||||
* \retval 0 on success
|
||||
*
|
||||
* \note The caller must ast_free() the returned URI string
|
||||
*/
|
||||
int ast_sip_redirect_get_next_uri(struct ast_sip_redirect_state *state, char **uri_out);
|
||||
|
||||
/*!
|
||||
* \brief Check if a URI would create a redirect loop
|
||||
*
|
||||
* \param state The redirect state
|
||||
* \param uri The URI to check
|
||||
*
|
||||
* \retval 0 if URI is safe (not visited)
|
||||
* \retval 1 if URI would create a loop (already visited)
|
||||
*/
|
||||
int ast_sip_redirect_check_loop(const struct ast_sip_redirect_state *state, const char *uri);
|
||||
|
||||
/*!
|
||||
* \brief Get the current hop count
|
||||
*
|
||||
* \param state The redirect state
|
||||
*
|
||||
* \return The current hop count
|
||||
*/
|
||||
int ast_sip_redirect_get_hop_count(const struct ast_sip_redirect_state *state);
|
||||
|
||||
/*!
|
||||
* \brief Get the endpoint from the redirect state
|
||||
*
|
||||
* \param state The redirect state
|
||||
*
|
||||
* \return The endpoint (borrowed reference, do not cleanup)
|
||||
*/
|
||||
struct ast_sip_endpoint *ast_sip_redirect_get_endpoint(const struct ast_sip_redirect_state *state);
|
||||
|
||||
/*!
|
||||
* \brief Destroy a redirect state and free all resources
|
||||
*
|
||||
* \param state The redirect state to destroy
|
||||
*/
|
||||
void ast_sip_redirect_state_destroy(struct ast_sip_redirect_state *state);
|
||||
|
||||
#endif /* _RES_PJSIP_REDIRECT_H */
|
||||
@@ -3498,6 +3498,25 @@ struct pjsip_param *ast_sip_pjsip_uri_get_other_param(pjsip_uri *uri, const pj_s
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float ast_sip_parse_qvalue(const char *q_value) {
|
||||
char *end = NULL;
|
||||
float ret = strtof(q_value, &end);
|
||||
|
||||
if (end == q_value) {
|
||||
return -1.0f; /* Not a number. */
|
||||
}
|
||||
if ('\0' != *end) {
|
||||
return -1.0f; /* Extra characters at end of input. */
|
||||
}
|
||||
if (!isfinite(ret)) {
|
||||
return -1.0f; /* NaN or Infinity. */
|
||||
}
|
||||
if (ret > 1.0f || ret < 0.0f) {
|
||||
return -1.0f; /* Out of valid range. */
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
|
||||
const int ast_sip_hangup_sip2cause(int cause)
|
||||
{
|
||||
|
||||
@@ -691,6 +691,21 @@
|
||||
</enumlist>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="follow_redirect_methods">
|
||||
<since>
|
||||
<version>20.18.0</version>
|
||||
<version>22.8.0</version>
|
||||
<version>23.2.0</version>
|
||||
</since>
|
||||
<synopsis>Follow 3XX redirect responses for the defined SIP methods.</synopsis>
|
||||
<description><para>
|
||||
This is a comma-delimited, case-insensitive list of SIP methods for which redirects are followed.
|
||||
When a redirect response is received for a SIP request, the redirect is only followed if the
|
||||
method is listed in this config option. For example <literal>MESSAGE</literal>. Currently, only
|
||||
<literal>MESSAGE</literal> is supported.
|
||||
</para>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="mailboxes">
|
||||
<since>
|
||||
<version>12.0.0</version>
|
||||
|
||||
@@ -601,6 +601,80 @@ static int redirect_method_to_str(const void *obj, const intptr_t *args, char **
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Mapping of SIP method names to their corresponding redirect flags
|
||||
*/
|
||||
struct redirect_method_map {
|
||||
const char *method_name;
|
||||
enum ast_sip_redirect_method flag;
|
||||
};
|
||||
|
||||
static const struct redirect_method_map redirect_method_mappings[] = {
|
||||
{ "message", AST_SIP_REDIRECT_METHOD_MESSAGE },
|
||||
};
|
||||
|
||||
static int follow_redirect_methods_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sip_endpoint *endpoint = obj;
|
||||
char *methods;
|
||||
char *method;
|
||||
int i;
|
||||
|
||||
/* Clear any existing flags */
|
||||
ast_clear_flag(&endpoint->follow_redirect_methods, ~0);
|
||||
|
||||
if (ast_strlen_zero(var->value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
methods = ast_strdupa(var->value);
|
||||
while ((method = ast_strsep(&methods, ',', AST_STRSEP_TRIM))) {
|
||||
int found = 0;
|
||||
|
||||
/* Look up the method in our mapping table */
|
||||
for (i = 0; i < ARRAY_LEN(redirect_method_mappings); i++) {
|
||||
if (!strcasecmp(method, redirect_method_mappings[i].method_name)) {
|
||||
ast_set_flag(&endpoint->follow_redirect_methods, redirect_method_mappings[i].flag);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
ast_log(LOG_ERROR, "Unrecognized SIP method '%s' for follow_redirect_methods on endpoint %s\n",
|
||||
method, ast_sorcery_object_get_id(endpoint));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int follow_redirect_methods_to_str(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
const struct ast_sip_endpoint *endpoint = obj;
|
||||
struct ast_str *str = ast_str_create(64);
|
||||
int first = 1;
|
||||
int i;
|
||||
|
||||
if (!str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Iterate through all supported methods and append if flag is set */
|
||||
for (i = 0; i < ARRAY_LEN(redirect_method_mappings); i++) {
|
||||
if (ast_test_flag(&endpoint->follow_redirect_methods, redirect_method_mappings[i].flag)) {
|
||||
ast_str_append(&str, 0, "%s%s", first ? "" : ",", redirect_method_mappings[i].method_name);
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
*buf = ast_strdup(ast_str_buffer(str));
|
||||
ast_free(str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int direct_media_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sip_endpoint *endpoint = obj;
|
||||
@@ -2253,6 +2327,7 @@ int ast_res_pjsip_initialize_configuration(void)
|
||||
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_encryption_optimistic", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.encryption_optimistic));
|
||||
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "g726_non_standard", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.g726_non_standard));
|
||||
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "redirect_method", "user", redirect_method_handler, redirect_method_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "follow_redirect_methods", "", follow_redirect_methods_handler, follow_redirect_methods_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "set_var", "", set_var_handler, set_var_to_str, set_var_to_vl, 0, 0);
|
||||
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "message_context", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, message_context));
|
||||
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "accountcode", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, accountcode));
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2025, Commend International
|
||||
*
|
||||
* Maximilian Fridrich <m.fridrich@commend.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
#include <pjsip.h>
|
||||
|
||||
#include "asterisk/linkedlists.h"
|
||||
#include "asterisk/res_pjsip.h"
|
||||
#include "asterisk/res_pjsip_redirect.h"
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Visited URI tracking for redirect loop detection
|
||||
*/
|
||||
struct visited_uri {
|
||||
char uri[PJSIP_MAX_URL_SIZE];
|
||||
AST_LIST_ENTRY(visited_uri) list;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Redirect contact with q-value for prioritization
|
||||
*/
|
||||
struct redirect_contact {
|
||||
char uri[PJSIP_MAX_URL_SIZE];
|
||||
float q_value; /* q-value from Contact header, default 1.0 if not present */
|
||||
AST_LIST_ENTRY(redirect_contact) list;
|
||||
};
|
||||
|
||||
/*! \brief List of redirect contacts */
|
||||
AST_LIST_HEAD_NOLOCK(redirect_contact_list, redirect_contact);
|
||||
|
||||
/*!
|
||||
* \brief Redirect state structure
|
||||
*/
|
||||
struct ast_sip_redirect_state {
|
||||
struct ast_sip_endpoint *endpoint;
|
||||
int hop_count;
|
||||
AST_LIST_HEAD_NOLOCK(, visited_uri) visited_uris;
|
||||
struct redirect_contact_list pending_contacts;
|
||||
};
|
||||
|
||||
struct ast_sip_redirect_state *ast_sip_redirect_state_create(
|
||||
struct ast_sip_endpoint *endpoint,
|
||||
const char *initial_uri)
|
||||
{
|
||||
struct ast_sip_redirect_state *state;
|
||||
struct visited_uri *visited;
|
||||
|
||||
state = ast_calloc(1, sizeof(*state));
|
||||
if (!state) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->endpoint = ao2_bump(endpoint);
|
||||
state->hop_count = 0;
|
||||
AST_LIST_HEAD_INIT_NOLOCK(&state->visited_uris);
|
||||
AST_LIST_HEAD_INIT_NOLOCK(&state->pending_contacts);
|
||||
|
||||
/* Add the initial URI to visited list */
|
||||
if (initial_uri) {
|
||||
visited = ast_calloc(1, sizeof(*visited));
|
||||
if (visited) {
|
||||
ast_copy_string(visited->uri, initial_uri, sizeof(visited->uri));
|
||||
AST_LIST_INSERT_HEAD(&state->visited_uris, visited, list);
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Redirect: Memory allocation failed for endpoint '%s'. "
|
||||
"Redirect loop detection may be impaired.\n", ast_sorcery_object_get_id(state->endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Mapping of SIP method names to their corresponding redirect flags
|
||||
*/
|
||||
struct redirect_method_map {
|
||||
const char *method_name;
|
||||
enum ast_sip_redirect_method flag;
|
||||
};
|
||||
|
||||
static const struct redirect_method_map redirect_methods[] = {
|
||||
{ "MESSAGE", AST_SIP_REDIRECT_METHOD_MESSAGE },
|
||||
};
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Check if a SIP method is allowed to follow redirects
|
||||
*
|
||||
* \param endpoint The SIP endpoint
|
||||
* \param method_name The SIP method name from the CSeq header
|
||||
*
|
||||
* \retval 0 if method is not allowed to follow redirects
|
||||
* \retval 1 if method is allowed to follow redirects
|
||||
*/
|
||||
static int method_allowed_for_redirect(struct ast_sip_endpoint *endpoint, const pj_str_t *method_name)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Look up the method in our mapping table */
|
||||
for (i = 0; i < ARRAY_LEN(redirect_methods); i++) {
|
||||
if (pj_stricmp2(method_name, redirect_methods[i].method_name) == 0) {
|
||||
/* Method is recognized, check if it's allowed */
|
||||
if (ast_test_flag(&endpoint->follow_redirect_methods, redirect_methods[i].flag)) {
|
||||
return 1;
|
||||
} else {
|
||||
ast_log(LOG_NOTICE, "Received redirect for %s to endpoint '%s', "
|
||||
"but %s is not in follow_redirect_methods. Not following redirect.\n",
|
||||
redirect_methods[i].method_name,
|
||||
ast_sorcery_object_get_id(endpoint), redirect_methods[i].method_name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Method not recognized/supported for redirects */
|
||||
ast_log(LOG_NOTICE, "Received redirect for method %.*s to endpoint '%s', "
|
||||
"but this method is not supported in follow_redirect_methods. Not following redirect.\n",
|
||||
(int)method_name->slen, method_name->ptr, ast_sorcery_object_get_id(endpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_should_redirect(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
|
||||
{
|
||||
pjsip_msg *msg;
|
||||
pjsip_cseq_hdr *cseq;
|
||||
int status_code;
|
||||
|
||||
if (!rdata || !rdata->msg_info.msg || rdata->msg_info.msg->type != PJSIP_RESPONSE_MSG) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
msg = rdata->msg_info.msg;
|
||||
status_code = msg->line.status.code;
|
||||
|
||||
/* Check if it's a 3xx response */
|
||||
if (!PJSIP_IS_STATUS_IN_CLASS(status_code, 300)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Extract the method from the CSeq header */
|
||||
cseq = rdata->msg_info.cseq;
|
||||
if (!cseq) {
|
||||
ast_log(LOG_WARNING, "Received %d redirect for endpoint '%s', but no CSeq header found\n",
|
||||
status_code, ast_sorcery_object_get_id(endpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if this method is allowed to follow redirects */
|
||||
return method_allowed_for_redirect(endpoint, &cseq->method.name);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Check if a URI has already been visited (loop detection)
|
||||
*/
|
||||
static int is_uri_visited(const struct ast_sip_redirect_state *state, const char *uri)
|
||||
{
|
||||
struct visited_uri *visited;
|
||||
|
||||
AST_LIST_TRAVERSE(&state->visited_uris, visited, list) {
|
||||
if (!strcmp(visited->uri, uri)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Add a URI to the visited list
|
||||
*/
|
||||
static int add_visited_uri(struct ast_sip_redirect_state *state, const char *uri)
|
||||
{
|
||||
struct visited_uri *visited;
|
||||
|
||||
visited = ast_calloc(1, sizeof(*visited));
|
||||
if (!visited) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_copy_string(visited->uri, uri, sizeof(visited->uri));
|
||||
AST_LIST_INSERT_TAIL(&state->visited_uris, visited, list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Extract q-value from a Contact header
|
||||
*
|
||||
* \param contact The Contact header
|
||||
* \return The q-value (default 1.0 if not present or invalid)
|
||||
*/
|
||||
static float extract_q_value(const pjsip_contact_hdr *contact)
|
||||
{
|
||||
pjsip_param *param;
|
||||
static const pj_str_t Q_STR = { "q", 1 };
|
||||
|
||||
/* Search for q parameter in the contact header */
|
||||
param = pjsip_param_find(&contact->other_param, &Q_STR);
|
||||
if (!param) {
|
||||
/* No q parameter, use default */
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/* Parse the q value */
|
||||
if (param->value.slen > 0) {
|
||||
char q_buf[16];
|
||||
float q_val;
|
||||
int len = param->value.slen < sizeof(q_buf) - 1 ? param->value.slen : sizeof(q_buf) - 1;
|
||||
memcpy(q_buf, param->value.ptr, len);
|
||||
q_buf[len] = '\0';
|
||||
|
||||
q_val = ast_sip_parse_qvalue(q_buf);
|
||||
|
||||
return q_val < 0.0f ? 1.0f : q_val;
|
||||
}
|
||||
|
||||
/* Invalid q value, use default */
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Insert a contact into the sorted list by q-value (highest first)
|
||||
*
|
||||
* \param list The list to insert into
|
||||
* \param new_contact The contact to insert
|
||||
*/
|
||||
static void insert_contact_sorted(struct redirect_contact_list *list, struct redirect_contact *new_contact)
|
||||
{
|
||||
struct redirect_contact *contact;
|
||||
|
||||
/* Find the insertion point - contacts with higher q values come first */
|
||||
AST_LIST_TRAVERSE_SAFE_BEGIN(list, contact, list) {
|
||||
if (new_contact->q_value > contact->q_value) {
|
||||
/* Insert before this contact */
|
||||
AST_LIST_INSERT_BEFORE_CURRENT(new_contact, list);
|
||||
return;
|
||||
}
|
||||
}
|
||||
AST_LIST_TRAVERSE_SAFE_END;
|
||||
|
||||
/* If we get here, insert at the end */
|
||||
AST_LIST_INSERT_TAIL(list, new_contact, list);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Parse all Contact headers from a 3xx response and create a sorted list
|
||||
*
|
||||
* \param rdata The redirect response data
|
||||
* \param contacts List to populate with parsed contacts
|
||||
* \return Number of valid contacts found
|
||||
*/
|
||||
static int parse_redirect_contacts(pjsip_rx_data *rdata, struct redirect_contact_list *contacts, const struct ast_sip_redirect_state *state)
|
||||
{
|
||||
pjsip_contact_hdr *contact_hdr;
|
||||
pjsip_uri *contact_uri;
|
||||
int count = 0;
|
||||
void *start = NULL;
|
||||
|
||||
/* Iterate through all Contact headers */
|
||||
while ((contact_hdr = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, start))) {
|
||||
struct redirect_contact *redirect_contact;
|
||||
int len;
|
||||
|
||||
start = contact_hdr->next;
|
||||
|
||||
/* Enforce maximum contact limit to prevent resource exhaustion */
|
||||
if (count >= AST_SIP_MAX_REDIRECT_CONTACTS) {
|
||||
ast_log(LOG_WARNING, "Redirect: maximum Contact header limit (%d) reached for endpoint '%s'. Ignoring additional contacts\n",
|
||||
AST_SIP_MAX_REDIRECT_CONTACTS, ast_sorcery_object_get_id(state->endpoint));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!contact_hdr->uri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
contact_uri = (pjsip_uri *)pjsip_uri_get_uri(contact_hdr->uri);
|
||||
|
||||
/* Verify it's a SIP URI */
|
||||
if (!PJSIP_URI_SCHEME_IS_SIP(contact_uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact_uri)) {
|
||||
ast_debug(1, "Skipping non-SIP/SIPS Contact URI in redirect for endpoint '%s'\n", ast_sorcery_object_get_id(state->endpoint));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Allocate a new redirect_contact structure */
|
||||
redirect_contact = ast_calloc(1, sizeof(*redirect_contact));
|
||||
if (!redirect_contact) {
|
||||
ast_log(LOG_ERROR, "Failed to allocate memory for redirect contact for endpoint '%s'.\n", ast_sorcery_object_get_id(state->endpoint));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Print the URI */
|
||||
len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, contact_uri,
|
||||
redirect_contact->uri, sizeof(redirect_contact->uri) - 1);
|
||||
if (len < 1) {
|
||||
ast_debug(1, "Contact URI too long for redirect on endpoint '%s'. Skipping.\n", ast_sorcery_object_get_id(state->endpoint));
|
||||
ast_free(redirect_contact);
|
||||
continue;
|
||||
}
|
||||
redirect_contact->uri[len] = '\0';
|
||||
|
||||
/* Extract q-value */
|
||||
redirect_contact->q_value = extract_q_value(contact_hdr);
|
||||
|
||||
ast_debug(1, "Found redirect Contact: %s (q=%f) for endpoint '%s'.\n", redirect_contact->uri, redirect_contact->q_value,
|
||||
ast_sorcery_object_get_id(state->endpoint));
|
||||
|
||||
/* Insert into sorted list */
|
||||
insert_contact_sorted(contacts, redirect_contact);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int ast_sip_redirect_parse_3xx(pjsip_rx_data *rdata, struct ast_sip_redirect_state *state)
|
||||
{
|
||||
struct redirect_contact_list redirect_contacts;
|
||||
struct redirect_contact *contact;
|
||||
int contact_count;
|
||||
int status_code = rdata->msg_info.msg->line.status.code;
|
||||
|
||||
ast_debug(1, "Received %d redirect response on endpoint '%s'.\n", status_code, ast_sorcery_object_get_id(state->endpoint));
|
||||
|
||||
/* Check if redirect should be followed based on endpoint configuration */
|
||||
if (!ast_sip_should_redirect(state->endpoint, rdata)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check hop limit */
|
||||
if (state->hop_count >= AST_SIP_MAX_REDIRECT_HOPS) {
|
||||
ast_log(LOG_WARNING, "Redirect hop limit (%d) reached for endpoint '%s'. Not following redirect.\n",
|
||||
AST_SIP_MAX_REDIRECT_HOPS, ast_sorcery_object_get_id(state->endpoint));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse all Contact headers and sort by q-value */
|
||||
AST_LIST_HEAD_INIT_NOLOCK(&redirect_contacts);
|
||||
contact_count = parse_redirect_contacts(rdata, &redirect_contacts, state);
|
||||
|
||||
if (contact_count == 0) {
|
||||
ast_log(LOG_WARNING, "Received %d redirect without valid Contact headers for endpoint '%s'. Cannot follow redirect.\n",
|
||||
status_code, ast_sorcery_object_get_id(state->endpoint));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Filter out contacts that would create loops */
|
||||
AST_LIST_TRAVERSE_SAFE_BEGIN(&redirect_contacts, contact, list) {
|
||||
if (is_uri_visited(state, contact->uri)) {
|
||||
ast_log(LOG_WARNING, "Redirect: skipping Contact '%s' for endpoint '%s' (would create loop)\n", contact->uri,
|
||||
ast_sorcery_object_get_id(state->endpoint));
|
||||
AST_LIST_REMOVE_CURRENT(list);
|
||||
ast_free(contact);
|
||||
contact_count--;
|
||||
}
|
||||
}
|
||||
AST_LIST_TRAVERSE_SAFE_END;
|
||||
|
||||
if (contact_count == 0) {
|
||||
ast_log(LOG_WARNING, "Redirect: all Contact URIs would create loops for endpoint '%s'. Not following redirect.\n",
|
||||
ast_sorcery_object_get_id(state->endpoint));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Move all contacts to pending_contacts list */
|
||||
while ((contact = AST_LIST_REMOVE_HEAD(&redirect_contacts, list))) {
|
||||
AST_LIST_INSERT_TAIL(&state->pending_contacts, contact, list);
|
||||
}
|
||||
|
||||
/* Increment hop count */
|
||||
state->hop_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_redirect_get_next_uri(struct ast_sip_redirect_state *state, char **uri_out)
|
||||
{
|
||||
struct redirect_contact *contact;
|
||||
|
||||
if (!uri_out) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the first contact from the pending list */
|
||||
contact = AST_LIST_REMOVE_HEAD(&state->pending_contacts, list);
|
||||
if (!contact) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Allocate and return the URI string */
|
||||
*uri_out = ast_strdup(contact->uri);
|
||||
if (!*uri_out) {
|
||||
ast_free(contact);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Add to visited list to prevent loops */
|
||||
if (add_visited_uri(state, contact->uri)) {
|
||||
ast_log(LOG_WARNING, "Failed to add URI to visited list for endpoint '%s'. Loop detection may be impaired.\n",
|
||||
ast_sorcery_object_get_id(state->endpoint));
|
||||
}
|
||||
|
||||
ast_free(contact);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_redirect_check_loop(const struct ast_sip_redirect_state *state, const char *uri)
|
||||
{
|
||||
return is_uri_visited(state, uri);
|
||||
}
|
||||
|
||||
int ast_sip_redirect_get_hop_count(const struct ast_sip_redirect_state *state)
|
||||
{
|
||||
return state->hop_count;
|
||||
}
|
||||
|
||||
struct ast_sip_endpoint *ast_sip_redirect_get_endpoint(const struct ast_sip_redirect_state *state)
|
||||
{
|
||||
return state->endpoint;
|
||||
}
|
||||
|
||||
void ast_sip_redirect_state_destroy(struct ast_sip_redirect_state *state)
|
||||
{
|
||||
struct visited_uri *visited;
|
||||
struct redirect_contact *contact;
|
||||
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
ao2_cleanup(state->endpoint);
|
||||
|
||||
while ((visited = AST_LIST_REMOVE_HEAD(&state->visited_uris, list))) {
|
||||
ast_free(visited);
|
||||
}
|
||||
|
||||
while ((contact = AST_LIST_REMOVE_HEAD(&state->pending_contacts, list))) {
|
||||
ast_free(contact);
|
||||
}
|
||||
|
||||
ast_free(state);
|
||||
}
|
||||
@@ -213,32 +213,6 @@ void ast_sip_remove_headers_by_name_and_value(pjsip_msg *msg, const pj_str_t *hd
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Parses a string representing a q_value to a float.
|
||||
*
|
||||
* Valid q values must be in the range from 0.0 to 1.0 inclusively.
|
||||
*
|
||||
* \param q_value
|
||||
* \retval The parsed qvalue or -1.0 on failure.
|
||||
*/
|
||||
static float parse_qvalue(const char *q_value) {
|
||||
char *end;
|
||||
float ret = strtof(q_value, &end);
|
||||
|
||||
if (end == q_value) {
|
||||
/* Not a number. */
|
||||
return -1.0;
|
||||
} else if ('\0' != *end) {
|
||||
/* Extra character at end of input. */
|
||||
return -1.0;
|
||||
} else if (ret > 1.0 || ret < 0.0) {
|
||||
/* Out of valid range. */
|
||||
return -1.0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ast_sip_str_to_security_mechanism(struct ast_sip_security_mechanism **security_mechanism, const char *value) {
|
||||
struct ast_sip_security_mechanism *mech;
|
||||
char *param;
|
||||
@@ -267,7 +241,7 @@ int ast_sip_str_to_security_mechanism(struct ast_sip_security_mechanism **securi
|
||||
goto out;
|
||||
}
|
||||
if (!strncmp(param, "q=", 2)) {
|
||||
mech->qvalue = parse_qvalue(¶m[2]);
|
||||
mech->qvalue = ast_sip_parse_qvalue(¶m[2]);
|
||||
if (mech->qvalue < 0.0) {
|
||||
err = EINVAL;
|
||||
goto out;
|
||||
|
||||
Reference in New Issue
Block a user