2010-06-22 00:16:50 -04:00
/*
* FreeSWITCH Modular Media Switching Software Library / Soft - Switch Application
2011-01-05 10:08:55 -06:00
* Copyright ( C ) 2005 - 2011 , Anthony Minessale II < anthm @ freeswitch . org >
2010-06-22 00:16:50 -04:00
*
* Version : MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 ( the " License " ) ; you may not use this file except in compliance with
* the License . You may obtain a copy of the License at
* http : //www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an " AS IS " basis ,
* WITHOUT WARRANTY OF ANY KIND , either express or implied . See the License
* for the specific language governing rights and limitations under the
* License .
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft - Switch Application
*
* The Initial Developer of the Original Code is
* Anthony Minessale II < anthm @ freeswitch . org >
* Portions created by the Initial Developer are Copyright ( C )
* the Initial Developer . All Rights Reserved .
*
* Contributor ( s ) :
* Kevin Morizur < kmorizur @ avgs . ca >
* Mathieu Rene < mrene @ avgs . ca >
*
* mod_redis . c - - Redis limit backend
*
*/
# include <switch.h>
# include "credis.h"
SWITCH_MODULE_LOAD_FUNCTION ( mod_redis_load ) ;
SWITCH_MODULE_SHUTDOWN_FUNCTION ( mod_redis_shutdown ) ;
2010-10-11 13:42:58 -04:00
SWITCH_MODULE_DEFINITION ( mod_redis , mod_redis_load , mod_redis_shutdown , NULL ) ;
2010-06-22 00:16:50 -04:00
static struct {
char * host ;
int port ;
int timeout ;
} globals ;
static switch_xml_config_item_t instructions [ ] = {
/* parameter name type reloadable pointer default value options structure */
SWITCH_CONFIG_ITEM_STRING_STRDUP ( " host " , CONFIG_RELOAD , & globals . host , NULL , " localhost " , " Hostname for redis server " ) ,
SWITCH_CONFIG_ITEM ( " port " , SWITCH_CONFIG_INT , CONFIG_RELOADABLE , & globals . port , ( void * ) 6379 , NULL , NULL , NULL ) ,
SWITCH_CONFIG_ITEM ( " timeout " , SWITCH_CONFIG_INT , CONFIG_RELOADABLE , & globals . timeout , ( void * ) 10000 , NULL , NULL , NULL ) ,
SWITCH_CONFIG_ITEM_END ( )
} ;
/* HASH STUFF */
typedef struct {
switch_hash_t * hash ;
switch_mutex_t * mutex ;
} limit_redis_private_t ;
static switch_status_t redis_factory ( REDIS * redis )
{
if ( ! ( ( * redis ) = credis_connect ( globals . host , globals . port , globals . timeout ) ) ) {
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_ERROR , " Couldn't connect to redis server at %s:%d timeout:%d \n " , globals . host , globals . port , globals . timeout ) ;
return SWITCH_STATUS_FALSE ;
}
return SWITCH_STATUS_SUCCESS ;
}
/* \brief Enforces limit_redis restrictions
* \ param session current session
* \ param realm limit realm
* \ param id limit id
* \ param max maximum count
* \ param interval interval for rate limiting
* \ return SWITCH_TRUE if the access is allowed , SWITCH_FALSE if it isnt
*/
SWITCH_LIMIT_INCR ( limit_incr_redis )
{
switch_channel_t * channel = switch_core_session_get_channel ( session ) ;
limit_redis_private_t * pvt = NULL ;
int val , uuid_val ;
char * rediskey = NULL ;
char * uuid_rediskey = NULL ;
uint8_t increment = 1 ;
switch_status_t status = SWITCH_STATUS_SUCCESS ;
REDIS redis ;
if ( redis_factory ( & redis ) ! = SWITCH_STATUS_SUCCESS ) {
return SWITCH_STATUS_FALSE ;
}
/* Get the keys for redis server */
2011-02-02 15:43:26 -06:00
uuid_rediskey = switch_core_session_sprintf ( session , " %s_%s_%s " , switch_core_get_hostname ( ) , realm , resource ) ;
2010-06-22 00:16:50 -04:00
rediskey = switch_core_session_sprintf ( session , " %s_%s " , realm , resource ) ;
if ( ( pvt = switch_channel_get_private ( channel , " limit_redis " ) ) ) {
increment = ! switch_core_hash_find_locked ( pvt - > hash , rediskey , pvt - > mutex ) ;
} else {
/* This is the first limit check on this channel, create a hashtable, set our prviate data and add a state handler */
pvt = ( limit_redis_private_t * ) switch_core_session_alloc ( session , sizeof ( limit_redis_private_t ) ) ;
switch_core_hash_init ( & pvt - > hash , switch_core_session_get_pool ( session ) ) ;
switch_mutex_init ( & pvt - > mutex , SWITCH_MUTEX_NESTED , switch_core_session_get_pool ( session ) ) ;
switch_channel_set_private ( channel , " limit_redis " , pvt ) ;
}
if ( ! ( switch_core_hash_find_locked ( pvt - > hash , rediskey , pvt - > mutex ) ) ) {
switch_core_hash_insert_locked ( pvt - > hash , rediskey , rediskey , pvt - > mutex ) ;
}
if ( increment ) {
if ( credis_incr ( redis , rediskey , & val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_ERROR , " Couldn't increment value corresponding to %s \n " , rediskey ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
if ( max > 0 ) {
if ( val > max ) {
if ( credis_decr ( redis , rediskey , & val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't decrement value corresponding to %s \n " , rediskey ) ;
switch_goto_status ( SWITCH_STATUS_GENERR , end ) ;
} else {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_INFO , " Usage for %s exceeds maximum rate of %d \n " ,
rediskey , max ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
} else {
if ( credis_incr ( redis , uuid_rediskey , & uuid_val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't increment value corresponding to %s \n " , uuid_rediskey ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
}
}
}
/*
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG10 , " Limit incr redis : rediskey : %s val : %d max : %d \n " , rediskey , val , max ) ;
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG10 , " Limit incr redis : uuid_rediskey : %s uuid_val : %d max : %d \n " , uuid_rediskey , uuid_val , max ) ;
*/
end :
if ( redis ) {
credis_close ( redis ) ;
}
return status ;
}
/* !\brief Releases usage of a limit_redis-controlled ressource */
SWITCH_LIMIT_RELEASE ( limit_release_redis )
{
switch_channel_t * channel = switch_core_session_get_channel ( session ) ;
limit_redis_private_t * pvt = switch_channel_get_private ( channel , " limit_redis " ) ;
int val , uuid_val ;
switch_hash_index_t * hi ;
char * rediskey = NULL ;
char * uuid_rediskey = NULL ;
int status = SWITCH_STATUS_SUCCESS ;
REDIS redis ;
if ( ! pvt | | ! pvt - > hash ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " No hashtable for channel %s \n " , switch_channel_get_name ( channel ) ) ;
return SWITCH_STATUS_SUCCESS ;
}
if ( redis_factory ( & redis ) ! = SWITCH_STATUS_SUCCESS ) {
return SWITCH_STATUS_FALSE ;
}
switch_mutex_lock ( pvt - > mutex ) ;
/* clear for uuid */
if ( realm = = NULL & & resource = = NULL ) {
/* Loop through the channel's hashtable which contains mapping to all the limit_redis_item_t referenced by that channel */
while ( ( hi = switch_hash_first ( NULL , pvt - > hash ) ) ) {
void * p_val = NULL ;
const void * p_key ;
char * p_uuid_key = NULL ;
switch_ssize_t keylen ;
switch_hash_this ( hi , & p_key , & keylen , & p_val ) ;
if ( credis_decr ( redis , ( const char * ) p_key , & val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't decrement value corresponding to %s \n " , ( char * ) p_key ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
2011-02-02 15:43:26 -06:00
p_uuid_key = switch_core_session_sprintf ( session , " %s_%s " , switch_core_get_hostname ( ) , ( char * ) p_key ) ;
2010-06-22 00:16:50 -04:00
if ( credis_decr ( redis , p_uuid_key , & uuid_val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't decrement value corresponding to %s \n " , p_uuid_key ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
switch_core_hash_delete ( pvt - > hash , ( const char * ) p_key ) ;
/*
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG10 , " Limit release redis : rediskey : %s val : %d \n " , ( char * ) p_val , val ) ;
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG10 , " Limit incr redis : uuid_rediskey : %s uuid_val : %d \n " ,
p_uuid_key , uuid_val ) ; */
}
} else {
rediskey = switch_core_session_sprintf ( session , " %s_%s " , realm , resource ) ;
2011-02-02 15:43:26 -06:00
uuid_rediskey = switch_core_session_sprintf ( session , " %s_%s_%s " , switch_core_get_hostname ( ) , realm , resource ) ;
2010-06-22 00:16:50 -04:00
switch_core_hash_delete ( pvt - > hash , ( const char * ) rediskey ) ;
if ( credis_decr ( redis , rediskey , & val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't decrement value corresponding to %s \n " , rediskey ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
if ( credis_decr ( redis , uuid_rediskey , & uuid_val ) ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Couldn't decrement value corresponding to %s \n " , uuid_rediskey ) ;
switch_goto_status ( SWITCH_STATUS_FALSE , end ) ;
}
/*
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_INFO , " Limit release redis : rediskey : %s val : %d \n " , rediskey , val ) ;
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_INFO , " Limit incr redis : uuid_rediskey : %s uuid_val : %d \n " , uuid_rediskey , uuid_val ) ;
*/
}
end :
switch_mutex_unlock ( pvt - > mutex ) ;
if ( redis ) {
credis_close ( redis ) ;
}
return status ;
}
SWITCH_LIMIT_USAGE ( limit_usage_redis )
{
char * redis_key ;
char * str ;
REDIS redis ;
int usage ;
if ( redis_factory ( & redis ) ! = SWITCH_STATUS_SUCCESS ) {
return 0 ;
}
redis_key = switch_mprintf ( " %s_%s " , realm , resource ) ;
if ( credis_get ( redis , redis_key , & str ) ! = 0 ) {
usage = 0 ;
} else {
usage = atoi ( str ) ;
}
if ( redis ) {
credis_close ( redis ) ;
}
switch_safe_free ( redis_key ) ;
return usage ;
}
SWITCH_LIMIT_RESET ( limit_reset_redis )
{
REDIS redis ;
if ( redis_factory ( & redis ) = = SWITCH_STATUS_SUCCESS ) {
2011-02-02 15:43:26 -06:00
char * rediskey = switch_mprintf ( " %s_* " , switch_core_get_hostname ( ) ) ;
2010-06-22 00:16:50 -04:00
int dec = 0 , val = 0 , keyc ;
char * uuids [ 2000 ] ;
if ( ( keyc = credis_keys ( redis , rediskey , uuids , switch_arraylen ( uuids ) ) ) > 0 ) {
int i = 0 ;
2011-02-02 15:43:26 -06:00
int hostnamelen = strlen ( switch_core_get_hostname ( ) ) + 1 ;
2010-06-22 00:16:50 -04:00
for ( i = 0 ; i < keyc & & uuids [ i ] ; i + + ) {
const char * key = uuids [ i ] + hostnamelen ;
char * value ;
if ( strlen ( uuids [ i ] ) < = hostnamelen ) {
continue ; /* Sanity check */
}
credis_get ( redis , key , & value ) ;
dec = atoi ( value ) ;
credis_decrby ( redis , key , dec , & val ) ;
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_DEBUG , " DECR %s by %d. value is now %d \n " , key , dec , val ) ;
}
}
switch_safe_free ( rediskey ) ;
credis_close ( redis ) ;
return SWITCH_STATUS_SUCCESS ;
} else {
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_WARNING , " Couldn't check/clear old redis entries \n " ) ;
return SWITCH_STATUS_FALSE ;
}
}
SWITCH_LIMIT_STATUS ( limit_status_redis )
{
char * ret = switch_mprintf ( " This function is not yet available for Redis DB " ) ;
return ret ;
}
SWITCH_MODULE_LOAD_FUNCTION ( mod_redis_load )
{
switch_limit_interface_t * limit_interface = NULL ;
* module_interface = switch_loadable_module_create_module_interface ( pool , modname ) ;
if ( switch_xml_config_parse_module_settings ( " redis.conf " , SWITCH_FALSE , instructions ) ! = SWITCH_STATUS_SUCCESS ) {
return SWITCH_STATUS_FALSE ;
}
/* If FreeSWITCH was restarted and we still have active calls, decrement them so our global count stays valid */
limit_reset_redis ( ) ;
2010-07-20 22:21:32 -05:00
SWITCH_ADD_LIMIT ( limit_interface , " redis " , limit_incr_redis , limit_release_redis , limit_usage_redis , limit_reset_redis , limit_status_redis , NULL ) ;
2010-06-22 00:16:50 -04:00
return SWITCH_STATUS_SUCCESS ;
}
SWITCH_MODULE_SHUTDOWN_FUNCTION ( mod_redis_shutdown )
{
switch_xml_config_cleanup ( instructions ) ;
return SWITCH_STATUS_SUCCESS ;
}
/* For Emacs:
* Local Variables :
* mode : c
* indent - tabs - mode : t
* tab - width : 4
* c - basic - offset : 4
* End :
* For VIM :
* vim : set softtabstop = 4 shiftwidth = 4 tabstop = 4 :
*/