From 7461c83c5f1aff1ff56eed4b0af97b4b2a2bdab7 Mon Sep 17 00:00:00 2001 From: ionutrazvanionita Date: Thu, 17 Dec 2015 17:34:50 +0200 Subject: [PATCH] Slot Based Taildrop algorithm for ratelimit In this new algorithm a window is held per every pipe, which is defined by two parameters: window_size(seconds) and slot_period(microseconds). The number of slots is window_size*1000/slot_period. The window is updated with every call received and it's implemented as a circular vector, which modifies it's start based on the elements that were dropped. How is the window updated: * if no message comes in 2*window_time from when the window start was set , it means we can't use the values from any of the slots and we set the value from any of the slots to 0; the window start time is updated; * if the message comes in t=[start+window_time; start+2*window_time) it means that we can keep the values in slots in the interval [t-window_time;start+window_time) and drop the ones in [window_time; t-window_time); the window start time is updated; * it the message come in t=[start; start+window_time) we don't do any update do the window, only to the value of the slot corresponding to time t; The algorithm also shares this value through the bin interface, allowing to have the values from all the replicas. Also, before sending the value through the interface, the replica checks the window in order to send only the values in valid slots. Two different functions were made because of efficiency reasons. When replicating we only do some very short loops or maybe nothing. --- modules/ratelimit/README | 61 ++++++++++-- modules/ratelimit/doc/ratelimit_admin.xml | 91 +++++++++++++++--- modules/ratelimit/ratelimit.c | 112 +++++++++++++++++++++- modules/ratelimit/ratelimit.h | 18 +++- modules/ratelimit/ratelimit_helper.c | 68 ++++++++++++- 5 files changed, 318 insertions(+), 32 deletions(-) diff --git a/modules/ratelimit/README b/modules/ratelimit/README index c5ca97aeeb4..8dcd619c124 100644 --- a/modules/ratelimit/README +++ b/modules/ratelimit/README @@ -43,7 +43,8 @@ Razvan Crainea 1.3.1. Tail Drop Algorithm (TAILDROP) 1.3.2. Random Early Detection Algorithm (RED) - 1.3.3. Network Algorithm (NETWORK) + 1.3.3. Slot Based Taildropping (SBT) + 1.3.4. Network Algorithm (NETWORK) 1.4. Dynamic Rate Limiting Algorithms @@ -69,6 +70,8 @@ Razvan Crainea 1.6.11. accept_pipes_from (integer) 1.6.12. accept_pipes_timeout (integer) 1.6.13. repl_pipes_auth_check (int) + 1.6.14. window_size (int) + 1.6.15. slot_period (int) 1.7. Exported Functions @@ -103,9 +106,11 @@ Razvan Crainea 1.11. Set accept_pipes_from parameter 1.12. Set accept_pipes_timeout parameter 1.13. Set repl_pipes_auth_check parameter - 1.14. rl_check usage - 1.15. rl_dec_count usage - 1.16. rl_reset_count usage + 1.14. Set window_size parameter + 1.15. Set slot_period parameter + 1.16. rl_check usage + 1.17. rl_dec_count usage + 1.18. rl_reset_count usage Chapter 1. Admin Guide @@ -194,7 +199,21 @@ Chapter 1. Admin Guide messages although the limit might not be reached within the interval. Decrease the timer interval if you encounter this. -1.3.3. Network Algorithm (NETWORK) +1.3.3. Slot Based Taildropping (SBT) + + SBT holds a window consisting of one or more slots. You can set + the window_size parameter(seconds) which means for how long we + should look back to count the calls and slot_period + parameter(miliseconds) which tells how granular the algorithm + should be. The number of slots will be window_size/slot_period. + If, for example, you have window_size= slot_period=1 second, + then after each second you shall lose the call count, but if + you set the slot_period to 100 microseconds, then when your + call will be outside the window, the calls in the first 100 + microseconds shall be dropped, and the rest in the next 900 + shall be kept. + +1.3.4. Network Algorithm (NETWORK) This algorithm relies on information provided by network interfaces. The total amount of bytes waiting to be consumed on @@ -415,6 +434,32 @@ modparam("ratelimit", "accept_pipes_timeout", 1) modparam("dialog", "repl_pipes_auth_check", 1) ... +1.6.14. window_size (int) + + How long the history in SBT should be in seconds. + + Default value is “10”. + + Example 1.14. Set window_size parameter +... +modparam("dialog", "window_size", 5) +... + +1.6.15. slot_period (int) + + Value of one slot in microseconds. This parameter determines + how granular the algorithm should be. The number of slots will + be determined by window_size/slot_period. + + Default value is “200”. + + Example 1.15. Set slot_period parameter +... +modparam("dialog", "window_size", 5) +#we will have 50 slots of 100 microseconds +modparam("dialog", "slot_period", 100) +... + 1.7. Exported Functions 1.7.1. rl_check(name, limit[, algorithm]) @@ -446,7 +491,7 @@ modparam("dialog", "repl_pipes_auth_check", 1) This function can be used from REQUEST_ROUTE. - Example 1.14. rl_check usage + Example 1.16. rl_check usage ... # perform a pipe match for all INVITE methods using RED algorith m @@ -475,7 +520,7 @@ m This function can be used from REQUEST_ROUTE. - Example 1.15. rl_dec_count usage + Example 1.17. rl_dec_count usage ... if (!rl_check("gw_$ru", "100", "TAILDROP")) { exit; @@ -494,7 +539,7 @@ m This function can be used from REQUEST_ROUTE. - Example 1.16. rl_reset_count usage + Example 1.18. rl_reset_count usage ... if (!rl_check("gw_$ru", "100", "TAILDROP")) { exit; diff --git a/modules/ratelimit/doc/ratelimit_admin.xml b/modules/ratelimit/doc/ratelimit_admin.xml index 114756bb517..54e4d32456c 100644 --- a/modules/ratelimit/doc/ratelimit_admin.xml +++ b/modules/ratelimit/doc/ratelimit_admin.xml @@ -1,17 +1,17 @@ - + &adminguide; - +
Overview This module implements rate limiting for SIP requests. In contrast to the PIKE module this limits the flow based on a per SIP request type - basis and not per source IP. The latest sources allow you to + basis and not per source IP. The latest sources allow you to dynamically group several messages into some entities and limit the - traffic based on them. The MI interface can be used to change + traffic based on them. The MI interface can be used to change tunables while running OpenSIPS. @@ -36,10 +36,10 @@ Distributed limiting is useful when the rate limit should be - performed not only on a specific node, but on the entire platform. - The internal limiting data will no longer be kept on each &osips; + performed not only on a specific node, but on the entire platform. + The internal limiting data will no longer be kept on each &osips; instance. It will be stored in a distributed Key-Value database and - queried by each instance before deciding if a SIP message + queried by each instance before deciding if a SIP message should be blocked or not. @@ -106,6 +106,23 @@ encounter this.
+
+ Slot Based Taildropping (SBT) + + SBT holds a window consisting of one or more slots. You can set the + window_size parameter(seconds) which means for + how long we should look back to count the calls and + slot_period parameter(miliseconds) which tells + how granular the algorithm should be. The number of slots will be + window_size/slot_period. + If, for example, you have window_size= + slot_period=1 second, then after each second + you shall lose the call count, but if you set the + slot_period to 100 microseconds, then when your + call will be outside the window, the calls in the first 100 microseconds + shall be dropped, and the rest in the next 900 shall be kept. + +
Network Algorithm (NETWORK) @@ -113,7 +130,7 @@ The total amount of bytes waiting to be consumed on all the network interfaces is retrieved once every timer_interval seconds. If the returned amount exceeds the limit specified in the modparam, - rl_check returns an error. + rl_check returns an error.
@@ -175,7 +192,7 @@
External Libraries or Applications - The following libraries or applications must be installed before + The following libraries or applications must be installed before running &osips; with this module loaded: @@ -218,7 +235,7 @@ modparam("ratelimit", "timer_interval", 5) <varname>expire_time</varname> (integer) This parameter specifies how long a pipe should be kept in memory - until deleted. + until deleted. @@ -258,7 +275,7 @@ modparam("ratelimit", "hash_size", 512)
<varname>default_algorithm</varname> (string) - Specifies which algorithm should be assumed in case it isn't + Specifies which algorithm should be assumed in case it isn't explicitly specified in the rl_check function. @@ -461,7 +478,53 @@ modparam("dialog", "repl_pipes_auth_check", 1)
- + +
+ <varname>window_size</varname> (int) + + How long the history in SBT should be in seconds. + + + + Default value is 10. + + + + Set <varname>window_size</varname> parameter + +... +modparam("dialog", "window_size", 5) +... + + +
+ +
+ <varname>slot_period</varname> (int) + + Value of one slot in microseconds. This parameter determines + how granular the algorithm should be. The number of slots will be + determined by window_size/slot_period. + + + + Default value is 200. + + + + Set <varname>slot_period</varname> parameter + +... +modparam("dialog", "window_size", 5) +#we will have 50 slots of 100 microseconds +modparam("dialog", "slot_period", 100) +... + + +
+ + +
Exported Functions @@ -473,7 +536,7 @@ modparam("dialog", "repl_pipes_auth_check", 1) Check the current request against the pipe identified by name and changes/updates the limit. If no pipe is found, then a new one is - created with the specified limit and algorithm, if specified. If the + created with the specified limit and algorithm, if specified. If the algorithm parameter doesn't exist, the default one is used. @@ -506,7 +569,7 @@ modparam("dialog", "repl_pipes_auth_check", 1) algorithm - this is parameter is optional and reffers to the algorithm used to check the pipe. If it is - not set, the default value is used. It accepts a string or a + not set, the default value is used. It accepts a string or a pseudovariable. diff --git a/modules/ratelimit/ratelimit.c b/modules/ratelimit/ratelimit.c index 12c6244ed1b..d7aa7e05bbf 100644 --- a/modules/ratelimit/ratelimit.c +++ b/modules/ratelimit/ratelimit.c @@ -68,6 +68,9 @@ int rl_repl_cluster = 0; int accept_repl_pipes_timeout = 10; int repl_pipes_auth_check = 0; +int rl_window_size=10; /* how many seconds the window shall hold*/ +int rl_slot_period=200; /* how many milisecs a slot from the window has */ + static str db_url = {0,0}; str db_prefix = str_init("rl_pipe_"); @@ -140,6 +143,8 @@ static param_export_t params[] = { { "replicate_pipes_to", INT_PARAM, &rl_repl_cluster }, { "accept_pipes_timeout", INT_PARAM, &accept_repl_pipes_timeout }, { "repl_pipes_auth_check", INT_PARAM, &repl_pipes_auth_check }, + { "window_size", INT_PARAM, &rl_window_size}, + { "slot_period", INT_PARAM, &rl_slot_period}, { 0, 0, 0} }; @@ -346,7 +351,7 @@ static int mod_init(void) if (repl_pipes_auth_check < 0) repl_pipes_auth_check = 0; - + if ( (rl_repl_cluster || accept_repl_pipes) && load_clusterer_api(&clusterer_api) != 0 ){ LM_DBG("failed to find clusterer API - is clusterer module loaded?\n"); return -1; @@ -417,8 +422,8 @@ static int mod_init(void) LM_ERR("cannot init bin replication\n"); return -1; } - - + + return 0; } @@ -477,6 +482,105 @@ int hash[100] = {18, 50, 51, 39, 49, 68, 8, 78, 61, 75, 53, 32, 45, 77, 31, 54, 33, 92, 76, 85, 5, 72, 9, 83, 56, 17, 95, 55, 80, 98, 66, 14, 16, 38, 71, 23, 2, 67, 36, 65, 27, 1, 19, 59, 89, 48}; +/** + * the algorithm keeps a circular window of requests in a fixed size buffer + * + * @param pipe containing the window + * @param update whether or not to inc call number + * @return number of calls in the window + */ +static inline int hist_check(rl_pipe_t *pipe) +{ + #define U2MILI(__usec__) (__usec__/1000) + #define S2MILI(__sec__) (__sec__ *1000) + int i; + int count; + int first_good_index; + int rl_win_ms = rl_window_size * 1000; + + + unsigned long long now_total, start_total; + + struct timeval tv; + + /* first get values from our beloved replicated friends + * current pipe counter will be calculated after this + * iteration; no need for the old one */ + pipe->counter=0; + count = rl_get_all_counters(pipe); + + gettimeofday(&tv, NULL); + if (pipe->rwin.start_time.tv_sec == 0) { + /* the lucky one to come first here */ + pipe->rwin.start_time = tv; + pipe->rwin.start_index = 0; + + /* we know it starts from 0 because we did memset when created*/ + pipe->rwin.window[pipe->rwin.start_index]++; + } else { + start_total = S2MILI(pipe->rwin.start_time.tv_sec) + + U2MILI(pipe->rwin.start_time.tv_usec); + + now_total = S2MILI(tv.tv_sec) + U2MILI(tv.tv_usec); + + /* didn't do any update to the window for "2*window_size" secs + * we can't use any elements from the vector + * the window is invalidated; very unlikely to happen*/ + if (now_total - start_total >= 2*rl_win_ms) { + memset(pipe->rwin.window, 0, + pipe->rwin.window_size * sizeof(long int)); + + pipe->rwin.start_index = 0; + pipe->rwin.start_time = tv; + pipe->rwin.window[pipe->rwin.start_index]++; + } else if (now_total - start_total >= rl_win_ms) { + /* current time in interval [window_size; 2*window_size) + * all the elements in [start_time; (ctime-window_size+1) are + * invalidated(set to 0) + * */ + /* the first window index not to be set to 0 + * number of slots from the start_index*/ + first_good_index = ((((now_total - rl_win_ms) - start_total) + /rl_slot_period + 1) + pipe->rwin.start_index) % + pipe->rwin.window_size; + + /* the new start time will be the start time of the first slot */ + start_total = (now_total - rl_win_ms) - + (now_total - rl_win_ms)%rl_slot_period+ rl_slot_period; + + pipe->rwin.start_time.tv_sec = start_total/1000; + pipe->rwin.start_time.tv_usec = (start_total%1000)*1000; + + + for (i=pipe->rwin.start_index; i != first_good_index; + i=(i+1)%pipe->rwin.window_size) + pipe->rwin.window[i] = 0; + + pipe->rwin.start_index = first_good_index; + + /* count current call; it will be the last element in the window */ + pipe->rwin.window[(pipe->rwin.start_index) + + (pipe->rwin.window_size-1) % pipe->rwin.window_size]++; + + } else { /* now_total - start_total < rl_win_ms */ + /* no need to modify the window, the value is inside it; + * we just need to increment the number of calls for + * the current slot*/ + pipe->rwin.window[(now_total-start_total)/rl_slot_period]++; + } + } + + /* count the total number of calls in the window */ + for (i=0; i < pipe->rwin.window_size; i++) + pipe->counter += pipe->rwin.window[i]; + + count += pipe->counter; + + return count > pipe->limit ? -1 : 1; + + #undef U2MILI + #undef S2MILI +} /** * runs the pipe's algorithm @@ -502,6 +606,8 @@ int rl_pipe_check(rl_pipe_t *pipe) return pipe->load; case PIPE_ALGO_FEEDBACK: return (hash[counter % 100] < *drop_rate) ? -1 : 1; + case PIPE_ALGO_HISTORY: + return hist_check(pipe); default: LM_ERR("ratelimit algorithm %d not implemented\n", pipe->algo); } diff --git a/modules/ratelimit/ratelimit.h b/modules/ratelimit/ratelimit.h index d7f3f44f1f3..a025b7f71c7 100644 --- a/modules/ratelimit/ratelimit.h +++ b/modules/ratelimit/ratelimit.h @@ -43,7 +43,8 @@ typedef enum { PIPE_ALGO_TAILDROP, PIPE_ALGO_RED, PIPE_ALGO_FEEDBACK, - PIPE_ALGO_NETWORK + PIPE_ALGO_NETWORK, + PIPE_ALGO_HISTORY } rl_algo_t; typedef struct rl_repl_counter { @@ -53,6 +54,17 @@ typedef struct rl_repl_counter { struct rl_repl_counter *next; } rl_repl_counter_t; + +typedef struct rl_window { + int window_size; /* how big the window array is */ + int start_index; /* where the window starts; the window uses + * a circular buffer so we will need to know + * where is the start of the buffer */ + struct timeval start_time; /* time from where the window starts */ + + long int *window; /* actual array of messages */ +} rl_window_t; + typedef struct rl_pipe { int limit; /* limit used by algorithm */ int counter; /* countes the accesses */ @@ -63,6 +75,7 @@ typedef struct rl_pipe { rl_algo_t algo; /* the algorithm used */ unsigned long last_used; /* timestamp when the pipe was last accessed */ rl_repl_counter_t *dsts; /* counters per destination */ + rl_window_t rwin; /* window of requests */ } rl_pipe_t; typedef struct rl_repl_dst { @@ -80,7 +93,6 @@ typedef struct { unsigned int locks_no; } rl_big_htable; - extern gen_lock_t * rl_lock; extern rl_big_htable rl_htable; extern int rl_timer_interval; @@ -94,6 +106,8 @@ extern int accept_repl_pipes; extern int accept_repl_pipes_timeout; extern int repl_pipes_auth_check; extern int rl_repl_cluster; +extern int rl_window_size; +extern int rl_slot_period; struct clusterer_binds clusterer_api; diff --git a/modules/ratelimit/ratelimit_helper.c b/modules/ratelimit/ratelimit_helper.c index f1fbf63f3be..2069a353334 100644 --- a/modules/ratelimit/ratelimit_helper.c +++ b/modules/ratelimit/ratelimit_helper.c @@ -277,6 +277,7 @@ struct { { str_init("TAILDROP"), PIPE_ALGO_TAILDROP}, { str_init("FEEDBACK"), PIPE_ALGO_FEEDBACK}, { str_init("NETWORK"), PIPE_ALGO_NETWORK}, + { str_init("SBT"), PIPE_ALGO_HISTORY}, { { 0, 0}, 0 }, @@ -374,7 +375,9 @@ int w_rl_check_3(struct sip_msg *_m, char *_n, char *_l, char *_a) if (!*pipe) { /* allocate new pipe */ - *pipe = shm_malloc(sizeof(rl_pipe_t)); + *pipe = shm_malloc(sizeof(rl_pipe_t) + + /* memory for the window */ + (rl_window_size*1000) / rl_slot_period * sizeof(long int)); if (!*pipe) { LM_ERR("no more shm memory\n"); goto release; @@ -385,6 +388,10 @@ int w_rl_check_3(struct sip_msg *_m, char *_n, char *_l, char *_a) if (algo == PIPE_ALGO_NETWORK) should_update = 1; (*pipe)->algo = (algo == PIPE_ALGO_NOP) ? rl_default_algo : algo; + (*pipe)->rwin.window = (long int *)((*pipe) + 1); + (*pipe)->rwin.window_size = rl_window_size * 1000 / rl_slot_period; + memset((*pipe)->rwin.window, 0, + (*pipe)->rwin.window_size * sizeof(long int)); } else { LM_DBG("Pipe %.*s found: %p - last used %lu\n", name.len, name.s, *pipe, (*pipe)->last_used); @@ -790,14 +797,14 @@ int w_rl_reset(struct sip_msg *_m, char *_n) static rl_repl_counter_t* find_destination(rl_pipe_t *pipe, int machine_id) { rl_repl_counter_t *head; - + head = pipe->dsts; while(head != NULL){ if( head->machine_id == machine_id ) break; head=head->next; } - + if(head == NULL){ head = shm_malloc(sizeof(rl_repl_counter_t)); if(head == NULL){ @@ -830,7 +837,7 @@ void rl_rcv_bin(int packet_type, struct receive_info *ri, int server_id) unsigned int hash_idx; time_t now; rl_repl_counter_t *destination; - + if (packet_type == SERVER_TEMP_DISABLED) { get_su_info(&ri->src_su.s, ip, port); LM_WARN("server: %s:%hu temporary disabled\n", ip, port); @@ -841,7 +848,7 @@ void rl_rcv_bin(int packet_type, struct receive_info *ri, int server_id) LM_INFO("server with clustererer id %d timeout\n", server_id); return; } - + if(get_bin_pkg_version() != BIN_VERSION){ LM_ERR("incompatible bin protocol version\n"); return; @@ -920,6 +927,57 @@ void rl_rcv_bin(int packet_type, struct receive_info *ri, int server_id) RL_RELEASE_LOCK(hash_idx); } +/* + * same as hist_check() in ratelimit.c but this one + * only counts, no updates on the window ==> faster + */ +static inline int hist_count(rl_pipe_t *pipe) +{ + /* Window ELement*/ + #define U2MILI(__usec__) (__usec__/1000) + #define S2MILI(__sec__) (__sec__ *1000) + int i; + int first_good_index; + int rl_win_ms = rl_window_size * 1000; + + int count=0; + + unsigned long long now_total, start_total; + + struct timeval tv; + + gettimeofday(&tv, NULL); + if (pipe->rwin.start_time.tv_sec == 0) { + return 0; + } else { + start_total = S2MILI(pipe->rwin.start_time.tv_sec) + + U2MILI(pipe->rwin.start_time.tv_usec); + now_total = S2MILI(tv.tv_sec) + U2MILI(tv.tv_usec); + + if (now_total - start_total >= 2*rl_win_ms) { + /* nothing here; window is expired */ + } else if (now_total - start_total >= rl_win_ms) { + first_good_index = ((((now_total - rl_win_ms) - start_total) + /rl_slot_period + 1) + pipe->rwin.start_index) % + pipe->rwin.window_size; + + count = 0; + for (i=first_good_index; i != pipe->rwin.start_index; + i=(i+1)%pipe->rwin.window_size) + count += pipe->rwin.window[i]; + + } else { + /* count all of them; valid window */ + for (i=0; i < pipe->rwin.window_size; i++) + count += pipe->rwin.window[i]; + } + } + return count; + + #undef U2MILI + #undef S2MILI +} + int rl_repl_init(void) {