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:
Maximilian Fridrich
2025-11-07 12:26:45 +01:00
parent 417f21e38d
commit 569b3ed995
9 changed files with 765 additions and 27 deletions
+3
View File
@@ -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: "")
@@ -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')
+23
View File
@@ -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 */
+143
View File
@@ -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 */
+19
View File
@@ -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)
{
+15
View File
@@ -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>
+75
View File
@@ -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));
+464
View File
@@ -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);
}
+1 -27
View File
@@ -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(&param[2]);
mech->qvalue = ast_sip_parse_qvalue(&param[2]);
if (mech->qvalue < 0.0) {
err = EINVAL;
goto out;