Asterisk - The Open Source Telephony Project GIT-master-4f2b068
Loading...
Searching...
No Matches
redirect.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2025, Commend International
5 *
6 * Maximilian Fridrich <m.fridrich@commend.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19#include "asterisk.h"
20
21#include <pjsip.h>
22
24#include "asterisk/res_pjsip.h"
26
27/*!
28 * \internal
29 * \brief Visited URI tracking for redirect loop detection
30 */
32 char uri[PJSIP_MAX_URL_SIZE];
34};
35
36/*!
37 * \internal
38 * \brief Redirect contact with q-value for prioritization
39 */
41 char uri[PJSIP_MAX_URL_SIZE];
42 float q_value; /* q-value from Contact header, default 1.0 if not present */
44};
45
46/*! \brief List of redirect contacts */
48
49/*!
50 * \brief Redirect state structure
51 */
58
61 const char *initial_uri)
62{
64 struct visited_uri *visited;
65
66 state = ast_calloc(1, sizeof(*state));
67 if (!state) {
68 return NULL;
69 }
70
71 state->endpoint = ao2_bump(endpoint);
72 state->hop_count = 0;
73 AST_LIST_HEAD_INIT_NOLOCK(&state->visited_uris);
74 AST_LIST_HEAD_INIT_NOLOCK(&state->pending_contacts);
75
76 /* Add the initial URI to visited list */
77 if (initial_uri) {
78 visited = ast_calloc(1, sizeof(*visited));
79 if (visited) {
80 ast_copy_string(visited->uri, initial_uri, sizeof(visited->uri));
81 AST_LIST_INSERT_HEAD(&state->visited_uris, visited, list);
82 } else {
83 ast_log(LOG_WARNING, "Redirect: Memory allocation failed for endpoint '%s'. "
84 "Redirect loop detection may be impaired.\n", ast_sorcery_object_get_id(state->endpoint));
85 }
86 }
87
88 return state;
89}
90
91/*!
92 * \brief Mapping of SIP method names to their corresponding redirect flags
93 */
95 const char *method_name;
97};
98
99static const struct redirect_method_map redirect_methods[] = {
100 { "MESSAGE", AST_SIP_REDIRECT_METHOD_MESSAGE },
101};
102
103/*!
104 * \internal
105 * \brief Check if a SIP method is allowed to follow redirects
106 *
107 * \param endpoint The SIP endpoint
108 * \param method_name The SIP method name from the CSeq header
109 *
110 * \retval 0 if method is not allowed to follow redirects
111 * \retval 1 if method is allowed to follow redirects
112 */
113static int method_allowed_for_redirect(struct ast_sip_endpoint *endpoint, const pj_str_t *method_name)
114{
115 int i;
116
117 /* Look up the method in our mapping table */
118 for (i = 0; i < ARRAY_LEN(redirect_methods); i++) {
119 if (pj_stricmp2(method_name, redirect_methods[i].method_name) == 0) {
120 /* Method is recognized, check if it's allowed */
122 return 1;
123 } else {
124 ast_log(LOG_NOTICE, "Received redirect for %s to endpoint '%s', "
125 "but %s is not in follow_redirect_methods. Not following redirect.\n",
128 return 0;
129 }
130 }
131 }
132
133 /* Method not recognized/supported for redirects */
134 ast_log(LOG_NOTICE, "Received redirect for method %.*s to endpoint '%s', "
135 "but this method is not supported in follow_redirect_methods. Not following redirect.\n",
136 (int)method_name->slen, method_name->ptr, ast_sorcery_object_get_id(endpoint));
137 return 0;
138}
139
140int ast_sip_should_redirect(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
141{
142 pjsip_msg *msg;
143 pjsip_cseq_hdr *cseq;
144 int status_code;
145
146 if (!rdata || !rdata->msg_info.msg || rdata->msg_info.msg->type != PJSIP_RESPONSE_MSG) {
147 return 0;
148 }
149
150 msg = rdata->msg_info.msg;
151 status_code = msg->line.status.code;
152
153 /* Check if it's a 3xx response */
154 if (!PJSIP_IS_STATUS_IN_CLASS(status_code, 300)) {
155 return 0;
156 }
157
158 /* Extract the method from the CSeq header */
159 cseq = rdata->msg_info.cseq;
160 if (!cseq) {
161 ast_log(LOG_WARNING, "Received %d redirect for endpoint '%s', but no CSeq header found\n",
162 status_code, ast_sorcery_object_get_id(endpoint));
163 return 0;
164 }
165
166 /* Check if this method is allowed to follow redirects */
167 return method_allowed_for_redirect(endpoint, &cseq->method.name);
168}
169
170/*!
171 * \internal
172 * \brief Check if a URI has already been visited (loop detection)
173 */
174static int is_uri_visited(const struct ast_sip_redirect_state *state, const char *uri)
175{
176 struct visited_uri *visited;
177
178 AST_LIST_TRAVERSE(&state->visited_uris, visited, list) {
179 if (!strcmp(visited->uri, uri)) {
180 return 1;
181 }
182 }
183 return 0;
184}
185
186/*!
187 * \internal
188 * \brief Add a URI to the visited list
189 */
190static int add_visited_uri(struct ast_sip_redirect_state *state, const char *uri)
191{
192 struct visited_uri *visited;
193
194 visited = ast_calloc(1, sizeof(*visited));
195 if (!visited) {
196 return -1;
197 }
198
199 ast_copy_string(visited->uri, uri, sizeof(visited->uri));
200 AST_LIST_INSERT_TAIL(&state->visited_uris, visited, list);
201
202 return 0;
203}
204
205/*!
206 * \internal
207 * \brief Extract q-value from a Contact header
208 *
209 * \param contact The Contact header
210 * \return The q-value (default 1.0 if not present or invalid)
211 */
212static float extract_q_value(const pjsip_contact_hdr *contact)
213{
214 pjsip_param *param;
215 static const pj_str_t Q_STR = { "q", 1 };
216
217 /* Search for q parameter in the contact header */
218 param = pjsip_param_find(&contact->other_param, &Q_STR);
219 if (!param) {
220 /* No q parameter, use default */
221 return 1.0f;
222 }
223
224 /* Parse the q value */
225 if (param->value.slen > 0) {
226 char q_buf[16];
227 float q_val;
228 int len = param->value.slen < sizeof(q_buf) - 1 ? param->value.slen : sizeof(q_buf) - 1;
229 memcpy(q_buf, param->value.ptr, len);
230 q_buf[len] = '\0';
231
232 q_val = ast_sip_parse_qvalue(q_buf);
233
234 return q_val < 0.0f ? 1.0f : q_val;
235 }
236
237 /* Invalid q value, use default */
238 return 1.0f;
239}
240
241/*!
242 * \internal
243 * \brief Insert a contact into the sorted list by q-value (highest first)
244 *
245 * \param list The list to insert into
246 * \param new_contact The contact to insert
247 */
248static void insert_contact_sorted(struct redirect_contact_list *list, struct redirect_contact *new_contact)
249{
250 struct redirect_contact *contact;
251
252 /* Find the insertion point - contacts with higher q values come first */
254 if (new_contact->q_value > contact->q_value) {
255 /* Insert before this contact */
257 return;
258 }
259 }
261
262 /* If we get here, insert at the end */
263 AST_LIST_INSERT_TAIL(list, new_contact, list);
264}
265
266/*!
267 * \internal
268 * \brief Parse all Contact headers from a 3xx response and create a sorted list
269 *
270 * \param rdata The redirect response data
271 * \param contacts List to populate with parsed contacts
272 * \return Number of valid contacts found
273 */
274static int parse_redirect_contacts(pjsip_rx_data *rdata, struct redirect_contact_list *contacts, const struct ast_sip_redirect_state *state)
275{
276 pjsip_contact_hdr *contact_hdr;
277 pjsip_uri *contact_uri;
278 int count = 0;
279 void *start = NULL;
280
281 /* Iterate through all Contact headers */
282 while ((contact_hdr = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, start))) {
284 int len;
285
286 start = contact_hdr->next;
287
288 /* Enforce maximum contact limit to prevent resource exhaustion */
289 if (count >= AST_SIP_MAX_REDIRECT_CONTACTS) {
290 ast_log(LOG_WARNING, "Redirect: maximum Contact header limit (%d) reached for endpoint '%s'. Ignoring additional contacts\n",
292 break;
293 }
294
295 if (!contact_hdr->uri) {
296 continue;
297 }
298
299 contact_uri = (pjsip_uri *)pjsip_uri_get_uri(contact_hdr->uri);
300
301 /* Verify it's a SIP URI */
302 if (!PJSIP_URI_SCHEME_IS_SIP(contact_uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact_uri)) {
303 ast_debug(1, "Skipping non-SIP/SIPS Contact URI in redirect for endpoint '%s'\n", ast_sorcery_object_get_id(state->endpoint));
304 continue;
305 }
306
307 /* Allocate a new redirect_contact structure */
309 if (!redirect_contact) {
310 ast_log(LOG_ERROR, "Failed to allocate memory for redirect contact for endpoint '%s'.\n", ast_sorcery_object_get_id(state->endpoint));
311 continue;
312 }
313
314 /* Print the URI */
315 len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, contact_uri,
317 if (len < 1) {
318 ast_debug(1, "Contact URI too long for redirect on endpoint '%s'. Skipping.\n", ast_sorcery_object_get_id(state->endpoint));
320 continue;
321 }
322 redirect_contact->uri[len] = '\0';
323
324 /* Extract q-value */
326
327 ast_debug(1, "Found redirect Contact: %s (q=%f) for endpoint '%s'.\n", redirect_contact->uri, redirect_contact->q_value,
329
330 /* Insert into sorted list */
332 count++;
333 }
334
335 return count;
336}
337
339{
340 struct redirect_contact_list redirect_contacts;
341 struct redirect_contact *contact;
342 int contact_count;
343 int status_code = rdata->msg_info.msg->line.status.code;
344
345 ast_debug(1, "Received %d redirect response on endpoint '%s'.\n", status_code, ast_sorcery_object_get_id(state->endpoint));
346
347 /* Check if redirect should be followed based on endpoint configuration */
348 if (!ast_sip_should_redirect(state->endpoint, rdata)) {
349 return -1;
350 }
351
352 /* Check hop limit */
353 if (state->hop_count >= AST_SIP_MAX_REDIRECT_HOPS) {
354 ast_log(LOG_WARNING, "Redirect hop limit (%d) reached for endpoint '%s'. Not following redirect.\n",
356 return -1;
357 }
358
359 /* Parse all Contact headers and sort by q-value */
360 AST_LIST_HEAD_INIT_NOLOCK(&redirect_contacts);
361 contact_count = parse_redirect_contacts(rdata, &redirect_contacts, state);
362
363 if (contact_count == 0) {
364 ast_log(LOG_WARNING, "Received %d redirect without valid Contact headers for endpoint '%s'. Cannot follow redirect.\n",
365 status_code, ast_sorcery_object_get_id(state->endpoint));
366 return -1;
367 }
368
369 /* Filter out contacts that would create loops */
370 AST_LIST_TRAVERSE_SAFE_BEGIN(&redirect_contacts, contact, list) {
371 if (is_uri_visited(state, contact->uri)) {
372 ast_log(LOG_WARNING, "Redirect: skipping Contact '%s' for endpoint '%s' (would create loop)\n", contact->uri,
375 ast_free(contact);
376 contact_count--;
377 }
378 }
380
381 if (contact_count == 0) {
382 ast_log(LOG_WARNING, "Redirect: all Contact URIs would create loops for endpoint '%s'. Not following redirect.\n",
384 return -1;
385 }
386
387 /* Move all contacts to pending_contacts list */
388 while ((contact = AST_LIST_REMOVE_HEAD(&redirect_contacts, list))) {
389 AST_LIST_INSERT_TAIL(&state->pending_contacts, contact, list);
390 }
391
392 /* Increment hop count */
393 state->hop_count++;
394
395 return 0;
396}
397
399{
400 struct redirect_contact *contact;
401
402 if (!uri_out) {
403 return -1;
404 }
405
406 /* Get the first contact from the pending list */
407 contact = AST_LIST_REMOVE_HEAD(&state->pending_contacts, list);
408 if (!contact) {
409 return -1;
410 }
411
412 /* Allocate and return the URI string */
413 *uri_out = ast_strdup(contact->uri);
414 if (!*uri_out) {
415 ast_free(contact);
416 return -1;
417 }
418
419 /* Add to visited list to prevent loops */
420 if (add_visited_uri(state, contact->uri)) {
421 ast_log(LOG_WARNING, "Failed to add URI to visited list for endpoint '%s'. Loop detection may be impaired.\n",
423 }
424
425 ast_free(contact);
426 return 0;
427}
428
430{
431 return is_uri_visited(state, uri);
432}
433
435{
436 return state->hop_count;
437}
438
440{
441 return state->endpoint;
442}
443
445{
446 struct visited_uri *visited;
447 struct redirect_contact *contact;
448
449 if (!state) {
450 return;
451 }
452
453 ao2_cleanup(state->endpoint);
454
455 while ((visited = AST_LIST_REMOVE_HEAD(&state->visited_uris, list))) {
456 ast_free(visited);
457 }
458
459 while ((contact = AST_LIST_REMOVE_HEAD(&state->pending_contacts, list))) {
460 ast_free(contact);
461 }
462
464}
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_log
Definition astobj2.c:42
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
A set of macros to manage forward-linked lists.
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
#define AST_LIST_INSERT_BEFORE_CURRENT(elm, field)
Inserts a list entry before the current entry during a traversal.
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
int ast_sip_should_redirect(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
Check if redirect should be followed based on endpoint configuration.
Definition redirect.c:140
int ast_sip_redirect_parse_3xx(pjsip_rx_data *rdata, struct ast_sip_redirect_state *state)
Parse a 3xx redirect response and extract contacts.
Definition redirect.c:338
int ast_sip_redirect_check_loop(const struct ast_sip_redirect_state *state, const char *uri)
Check if a URI would create a redirect loop.
Definition redirect.c:429
static const struct redirect_method_map redirect_methods[]
Definition redirect.c:99
static int is_uri_visited(const struct ast_sip_redirect_state *state, const char *uri)
Definition redirect.c:174
int ast_sip_redirect_get_next_uri(struct ast_sip_redirect_state *state, char **uri_out)
Get the next redirect URI to try.
Definition redirect.c:398
static void insert_contact_sorted(struct redirect_contact_list *list, struct redirect_contact *new_contact)
Definition redirect.c:248
struct ast_sip_endpoint * ast_sip_redirect_get_endpoint(const struct ast_sip_redirect_state *state)
Get the endpoint from the redirect state.
Definition redirect.c:439
static int method_allowed_for_redirect(struct ast_sip_endpoint *endpoint, const pj_str_t *method_name)
Definition redirect.c:113
static int add_visited_uri(struct ast_sip_redirect_state *state, const char *uri)
Definition redirect.c:190
static int parse_redirect_contacts(pjsip_rx_data *rdata, struct redirect_contact_list *contacts, const struct ast_sip_redirect_state *state)
Definition redirect.c:274
int ast_sip_redirect_get_hop_count(const struct ast_sip_redirect_state *state)
Get the current hop count.
Definition redirect.c:434
static float extract_q_value(const pjsip_contact_hdr *contact)
Definition redirect.c:212
void ast_sip_redirect_state_destroy(struct ast_sip_redirect_state *state)
Destroy a redirect state and free all resources.
Definition redirect.c:444
struct ast_sip_redirect_state * ast_sip_redirect_state_create(struct ast_sip_endpoint *endpoint, const char *initial_uri)
Create a new redirect state.
Definition redirect.c:59
ast_sip_redirect_method
SIP methods that are allowed to follow 3xx redirects.
Definition res_pjsip.h:758
@ AST_SIP_REDIRECT_METHOD_MESSAGE
Definition res_pjsip.h:760
float ast_sip_parse_qvalue(const char *q_value)
Parses a string representing a q_value to a float.
Definition res_pjsip.c:3501
#define AST_SIP_MAX_REDIRECT_HOPS
Maximum number of redirect hops allowed.
#define AST_SIP_MAX_REDIRECT_CONTACTS
Maximum number of redirect contacts to process.
#define NULL
Definition resample.c:96
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition sorcery.c:2381
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition strings.h:425
An entity with which Asterisk communicates.
Definition res_pjsip.h:1061
struct ast_flags follow_redirect_methods
Definition res_pjsip.h:1128
Redirect state structure.
Definition redirect.c:52
struct ast_sip_endpoint * endpoint
Definition redirect.c:53
struct redirect_contact_list pending_contacts
Definition redirect.c:56
struct ast_sip_redirect_state::@497 visited_uris
List of redirect contacts.
Definition redirect.c:47
struct redirect_contact * next
Definition redirect.c:43
char uri[PJSIP_MAX_URL_SIZE]
Definition redirect.c:41
struct redirect_contact::@496 list
Mapping of SIP method names to their corresponding redirect flags.
enum ast_sip_redirect_method flag
struct visited_uri::@495 list
char uri[PJSIP_MAX_URL_SIZE]
Definition redirect.c:32
#define ast_test_flag(p, flag)
Definition utils.h:64
#define ARRAY_LEN(a)
Definition utils.h:706