@ -24,6 +24,7 @@
# include "include/res_pjsip_private.h"
# include "asterisk/taskprocessor.h"
# include "asterisk/threadpool.h"
# include "asterisk/res_pjsip_cli.h"
static int distribute ( void * data ) ;
static pj_bool_t distributor ( pjsip_rx_data * rdata ) ;
@ -37,6 +38,26 @@ static pjsip_module distributor_mod = {
. on_rx_response = distributor ,
} ;
struct ast_sched_context * prune_context ;
/* From the auth/realm realtime column size */
# define MAX_REALM_LENGTH 40
static char default_realm [ MAX_REALM_LENGTH + 1 ] ;
# define DEFAULT_SUSPECTS_BUCKETS 53
static struct ao2_container * unidentified_requests ;
static unsigned int unidentified_count ;
static unsigned int unidentified_period ;
static unsigned int unidentified_prune_interval ;
static int using_auth_username ;
struct unidentified_request {
struct timeval first_seen ;
int count ;
char src_name [ ] ;
} ;
/*!
* \ internal
* \ brief Record the task ' s serializer name on the tdata structure .
@ -322,7 +343,7 @@ static int create_artificial_auth(void)
return - 1 ;
}
ast_string_field_set ( artificial_auth , realm , " asterisk " ) ;
ast_string_field_set ( artificial_auth , realm , default_realm ) ;
ast_string_field_set ( artificial_auth , auth_user , " " ) ;
ast_string_field_set ( artificial_auth , auth_pass , " " ) ;
artificial_auth - > type = AST_SIP_AUTH_TYPE_ARTIFICIAL ;
@ -359,27 +380,65 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void)
return artificial_endpoint ;
}
static void log_unidentified_request ( pjsip_rx_data * rdata )
static void log_unidentified_request ( pjsip_rx_data * rdata , unsigned int count , unsigned int period )
{
char from_buf [ PJSIP_MAX_URL_SIZE ] ;
char callid_buf [ PJSIP_MAX_URL_SIZE ] ;
pjsip_uri_print ( PJSIP_URI_IN_FROMTO_HDR , rdata - > msg_info . from - > uri , from_buf , PJSIP_MAX_URL_SIZE ) ;
ast_copy_pj_str ( callid_buf , & rdata - > msg_info . cid - > id , PJSIP_MAX_URL_SIZE ) ;
ast_log ( LOG_NOTICE , " Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found \n " ,
from_buf , rdata - > pkt_info . src_name , rdata - > pkt_info . src_port , callid_buf ) ;
if ( count ) {
ast_log ( LOG_NOTICE , " Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found "
" after %u tries in %.3f ms \n " ,
from_buf , rdata - > pkt_info . src_name , rdata - > pkt_info . src_port , callid_buf , count , period / 1000.0 ) ;
} else {
ast_log ( LOG_NOTICE , " Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found " ,
from_buf , rdata - > pkt_info . src_name , rdata - > pkt_info . src_port , callid_buf ) ;
}
}
static void check_endpoint ( pjsip_rx_data * rdata , struct unidentified_request * unid ,
const char * name )
{
int64_t ms = ast_tvdiff_ms ( ast_tvnow ( ) , unid - > first_seen ) ;
ao2_wrlock ( unid ) ;
unid - > count + + ;
if ( ms < ( unidentified_period * 1000 ) & & unid - > count > = unidentified_count ) {
log_unidentified_request ( rdata , unid - > count , ms ) ;
ast_sip_report_invalid_endpoint ( name , rdata ) ;
}
ao2_unlock ( unid ) ;
}
static pj_bool_t endpoint_lookup ( pjsip_rx_data * rdata )
{
struct ast_sip_endpoint * endpoint ;
struct unidentified_request * unid ;
int is_ack = rdata - > msg_info . msg - > line . req . method . id = = PJSIP_ACK_METHOD ;
endpoint = rdata - > endpt_info . mod_data [ endpoint_mod . id ] ;
if ( endpoint ) {
/*
* ao2_find with OBJ_UNLINK always write locks the container before even searching
* for the object . Since the majority case is that the object won ' t be found , do
* the find without OBJ_UNLINK to prevent the unnecessary write lock , then unlink
* if needed .
*/
if ( ( unid = ao2_find ( unidentified_requests , rdata - > pkt_info . src_name , OBJ_SEARCH_KEY ) ) ) {
ao2_unlink ( unidentified_requests , unid ) ;
ao2_ref ( unid , - 1 ) ;
}
return PJ_FALSE ;
}
endpoint = ast_sip_identify_endpoint ( rdata ) ;
if ( endpoint ) {
if ( ( unid = ao2_find ( unidentified_requests , rdata - > pkt_info . src_name , OBJ_SEARCH_KEY ) ) ) {
ao2_unlink ( unidentified_requests , unid ) ;
ao2_ref ( unid , - 1 ) ;
}
}
if ( ! endpoint & & ! is_ack ) {
char name [ AST_UUID_STR_LEN ] = " " ;
@ -397,8 +456,32 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
ast_copy_pj_str ( name , & sip_from - > user , sizeof ( name ) ) ;
}
log_unidentified_request ( rdata ) ;
ast_sip_report_invalid_endpoint ( name , rdata ) ;
if ( ( unid = ao2_find ( unidentified_requests , rdata - > pkt_info . src_name , OBJ_SEARCH_KEY ) ) ) {
check_endpoint ( rdata , unid , name ) ;
ao2_ref ( unid , - 1 ) ;
} else if ( using_auth_username ) {
ao2_wrlock ( unidentified_requests ) ;
/* The check again with the write lock held allows us to eliminate the DUPS_REPLACE and sort_fn */
if ( ( unid = ao2_find ( unidentified_requests , rdata - > pkt_info . src_name , OBJ_SEARCH_KEY | OBJ_NOLOCK ) ) ) {
check_endpoint ( rdata , unid , name ) ;
} else {
unid = ao2_alloc_options ( sizeof ( * unid ) + strlen ( rdata - > pkt_info . src_name ) + 1 , NULL ,
AO2_ALLOC_OPT_LOCK_RWLOCK ) ;
if ( ! unid ) {
ao2_unlock ( unidentified_requests ) ;
return PJ_TRUE ;
}
strcpy ( unid - > src_name , rdata - > pkt_info . src_name ) ; /* Safe */
unid - > first_seen = ast_tvnow ( ) ;
unid - > count = 1 ;
ao2_link_flags ( unidentified_requests , unid , OBJ_NOLOCK ) ;
}
ao2_ref ( unid , - 1 ) ;
ao2_unlock ( unidentified_requests ) ;
} else {
log_unidentified_request ( rdata , 0 , 0 ) ;
ast_sip_report_invalid_endpoint ( name , rdata ) ;
}
}
rdata - > endpt_info . mod_data [ endpoint_mod . id ] = endpoint ;
return PJ_FALSE ;
@ -413,6 +496,8 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
if ( ! is_ack & & ast_sip_requires_authentication ( endpoint , rdata ) ) {
pjsip_tx_data * tdata ;
struct unidentified_request * unid ;
pjsip_endpt_create_response ( ast_sip_get_pjsip_endpoint ( ) , rdata , 401 , NULL , & tdata ) ;
switch ( ast_sip_check_authentication ( endpoint , rdata , tdata ) ) {
case AST_SIP_AUTHENTICATION_CHALLENGE :
@ -421,6 +506,11 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
pjsip_endpt_send_response2 ( ast_sip_get_pjsip_endpoint ( ) , rdata , tdata , NULL , NULL ) ;
return PJ_TRUE ;
case AST_SIP_AUTHENTICATION_SUCCESS :
/* See note in endpoint_lookup about not holding an unnecessary write lock */
if ( ( unid = ao2_find ( unidentified_requests , rdata - > pkt_info . src_name , OBJ_SEARCH_KEY ) ) ) {
ao2_unlink ( unidentified_requests , unid ) ;
ao2_ref ( unid , - 1 ) ;
}
ast_sip_report_auth_success ( endpoint , rdata ) ;
pjsip_tx_data_dec_ref ( tdata ) ;
return PJ_FALSE ;
@ -480,31 +570,287 @@ struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata)
return endpoint ;
}
static int suspects_sort ( const void * obj , const void * arg , int flags )
{
const struct unidentified_request * object_left = obj ;
const struct unidentified_request * object_right = arg ;
const char * right_key = arg ;
int cmp ;
switch ( flags & OBJ_SEARCH_MASK ) {
case OBJ_SEARCH_OBJECT :
right_key = object_right - > src_name ;
/* Fall through */
case OBJ_SEARCH_KEY :
cmp = strcmp ( object_left - > src_name , right_key ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
cmp = strncmp ( object_left - > src_name , right_key , strlen ( right_key ) ) ;
break ;
default :
cmp = 0 ;
break ;
}
return cmp ;
}
static int suspects_compare ( void * obj , void * arg , int flags )
{
const struct unidentified_request * object_left = obj ;
const struct unidentified_request * object_right = arg ;
const char * right_key = arg ;
int cmp = 0 ;
switch ( flags & OBJ_SEARCH_MASK ) {
case OBJ_SEARCH_OBJECT :
right_key = object_right - > src_name ;
/* Fall through */
case OBJ_SEARCH_KEY :
if ( strcmp ( object_left - > src_name , right_key ) = = 0 ) {
cmp = CMP_MATCH | CMP_STOP ;
}
break ;
case OBJ_SEARCH_PARTIAL_KEY :
if ( strncmp ( object_left - > src_name , right_key , strlen ( right_key ) ) = = 0 ) {
cmp = CMP_MATCH ;
}
break ;
default :
cmp = 0 ;
break ;
}
return cmp ;
}
static int suspects_hash ( const void * obj , int flags ) {
const struct unidentified_request * object_left = obj ;
if ( flags & OBJ_SEARCH_OBJECT ) {
return ast_str_hash ( object_left - > src_name ) ;
} else if ( flags & OBJ_SEARCH_KEY ) {
return ast_str_hash ( obj ) ;
}
return - 1 ;
}
static struct ao2_container * cli_unid_get_container ( const char * regex )
{
struct ao2_container * s_container ;
s_container = ao2_container_alloc_list ( AO2_ALLOC_OPT_LOCK_NOLOCK , 0 ,
suspects_sort , suspects_compare ) ;
if ( ! s_container ) {
return NULL ;
}
if ( ao2_container_dup ( s_container , unidentified_requests , 0 ) ) {
ao2_ref ( s_container , - 1 ) ;
return NULL ;
}
return s_container ;
}
static int cli_unid_iterate ( void * container , ao2_callback_fn callback , void * args )
{
ao2_callback ( container , 0 , callback , args ) ;
return 0 ;
}
static void * cli_unid_retrieve_by_id ( const char * id )
{
return ao2_find ( unidentified_requests , id , OBJ_SEARCH_KEY ) ;
}
static const char * cli_unid_get_id ( const void * obj )
{
const struct unidentified_request * unid = obj ;
return unid - > src_name ;
}
static int cli_unid_print_header ( void * obj , void * arg , int flags )
{
struct ast_sip_cli_context * context = arg ;
RAII_VAR ( struct ast_sip_cli_formatter_entry * , formatter_entry , NULL , ao2_cleanup ) ;
int indent = CLI_INDENT_TO_SPACES ( context - > indent_level ) ;
int filler = CLI_LAST_TABSTOP - indent - 7 ;
ast_assert ( context - > output_buffer ! = NULL ) ;
ast_str_append ( & context - > output_buffer , 0 ,
" %*s: <IP Address%*.*s> <Count> <Age(sec)> \n " ,
indent , " Request " , filler , filler , CLI_HEADER_FILLER ) ;
return 0 ;
}
static int cli_unid_print_body ( void * obj , void * arg , int flags )
{
struct unidentified_request * unid = obj ;
struct ast_sip_cli_context * context = arg ;
int indent ;
int flexwidth ;
int64_t ms = ast_tvdiff_ms ( ast_tvnow ( ) , unid - > first_seen ) ;
ast_assert ( context - > output_buffer ! = NULL ) ;
indent = CLI_INDENT_TO_SPACES ( context - > indent_level ) ;
flexwidth = CLI_LAST_TABSTOP - 4 ;
ast_str_append ( & context - > output_buffer , 0 , " %*s: %-*.*s %7d %10.3f \n " ,
indent ,
" Request " ,
flexwidth , flexwidth ,
unid - > src_name , unid - > count , ms / 1000.0 ) ;
return 0 ;
}
static struct ast_cli_entry cli_commands [ ] = {
AST_CLI_DEFINE ( ast_sip_cli_traverse_objects , " Show PJSIP Unidentified Requests " ,
. command = " pjsip show unidentified_requests " ,
. usage = " Usage: pjsip show unidentified_requests \n "
" Show the PJSIP Unidentified Requests \n " ) ,
} ;
struct ast_sip_cli_formatter_entry * unid_formatter ;
static int expire_requests ( void * object , void * arg , int flags )
{
struct unidentified_request * unid = object ;
int * maxage = arg ;
int64_t ms = ast_tvdiff_ms ( ast_tvnow ( ) , unid - > first_seen ) ;
if ( ms > ( * maxage ) * 2 * 1000 ) {
return CMP_MATCH ;
}
return 0 ;
}
static int prune_task ( const void * data )
{
unsigned int maxage ;
ast_sip_get_unidentified_request_thresholds ( & unidentified_count , & unidentified_period , & unidentified_prune_interval ) ;
maxage = unidentified_period * 2 ;
ao2_callback ( unidentified_requests , OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK , expire_requests , & maxage ) ;
return unidentified_prune_interval * 1000 ;
}
static int clean_task ( const void * data )
{
return 0 ;
}
static void global_loaded ( const char * object_type )
{
char * identifier_order = ast_sip_get_endpoint_identifier_order ( ) ;
char * io_copy = ast_strdupa ( identifier_order ) ;
char * identify_method ;
ast_free ( identifier_order ) ;
using_auth_username = 0 ;
while ( ( identify_method = ast_strip ( strsep ( & io_copy , " , " ) ) ) ) {
if ( ! strcmp ( identify_method , " auth_username " ) ) {
using_auth_username = 1 ;
break ;
}
}
ast_sip_get_default_realm ( default_realm , sizeof ( default_realm ) ) ;
ast_sip_get_unidentified_request_thresholds ( & unidentified_count , & unidentified_period , & unidentified_prune_interval ) ;
/* Clean out the old task, if any */
ast_sched_clean_by_callback ( prune_context , prune_task , clean_task ) ;
if ( ast_sched_add_variable ( prune_context , unidentified_prune_interval * 1000 , prune_task , NULL , 1 ) < 0 ) {
return ;
}
}
/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
static struct ast_sorcery_observer global_observer = {
. loaded = global_loaded ,
} ;
int ast_sip_initialize_distributor ( void )
{
unidentified_requests = ao2_container_alloc_hash ( AO2_ALLOC_OPT_LOCK_RWLOCK , 0 ,
DEFAULT_SUSPECTS_BUCKETS , suspects_hash , NULL , suspects_compare ) ;
if ( ! unidentified_requests ) {
return - 1 ;
}
prune_context = ast_sched_context_create ( ) ;
if ( ! prune_context ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
if ( ast_sched_start_thread ( prune_context ) ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
ast_sorcery_observer_add ( ast_sip_get_sorcery ( ) , " global " , & global_observer ) ;
ast_sorcery_reload_object ( ast_sip_get_sorcery ( ) , " global " ) ;
if ( create_artificial_endpoint ( ) | | create_artificial_auth ( ) ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
if ( internal_sip_register_service ( & distributor_mod ) ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
if ( internal_sip_register_service ( & endpoint_mod ) ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
if ( internal_sip_register_service ( & auth_mod ) ) {
ast_sip_destroy_distributor ( ) ;
return - 1 ;
}
unid_formatter = ao2_alloc ( sizeof ( struct ast_sip_cli_formatter_entry ) , NULL ) ;
if ( ! unid_formatter ) {
ast_log ( LOG_ERROR , " Unable to allocate memory for unid_formatter \n " ) ;
return - 1 ;
}
unid_formatter - > name = " unidentified_request " ;
unid_formatter - > print_header = cli_unid_print_header ;
unid_formatter - > print_body = cli_unid_print_body ;
unid_formatter - > get_container = cli_unid_get_container ;
unid_formatter - > iterate = cli_unid_iterate ;
unid_formatter - > get_id = cli_unid_get_id ;
unid_formatter - > retrieve_by_id = cli_unid_retrieve_by_id ;
ast_sip_register_cli_formatter ( unid_formatter ) ;
ast_cli_register_multiple ( cli_commands , ARRAY_LEN ( cli_commands ) ) ;
return 0 ;
}
void ast_sip_destroy_distributor ( void )
{
ast_cli_unregister_multiple ( cli_commands , ARRAY_LEN ( cli_commands ) ) ;
ast_sip_unregister_cli_formatter ( unid_formatter ) ;
internal_sip_unregister_service ( & distributor_mod ) ;
internal_sip_unregister_service ( & endpoint_mod ) ;
internal_sip_unregister_service ( & auth_mod ) ;
ao2_cleanup ( artificial_auth ) ;
ao2_cleanup ( artificial_endpoint ) ;
ao2_cleanup ( unidentified_requests ) ;
ast_sorcery_observer_remove ( ast_sip_get_sorcery ( ) , " global " , & global_observer ) ;
if ( prune_context ) {
ast_sched_context_destroy ( prune_context ) ;
}
}