/** * runs the pipe's algorithm * (expects rl_lock to be taken) * \return -1 if drop needed, 1 if allowed */ int rl_pipe_check(rl_pipe_t *pipe) { unsigned counter = rl_get_all_counters(pipe); switch (pipe->algo) { case PIPE_ALGO_NOP: LM_ERR("no algorithm defined for this pipe\n"); return 1; case PIPE_ALGO_TAILDROP: return (counter <= pipe->limit * (rl_limit_per_interval ? 1 : rl_timer_interval)) ? 1 : -1; case PIPE_ALGO_RED: if (!pipe->load) return 1; return counter % pipe->load ? -1 : 1; case PIPE_ALGO_NETWORK: return pipe->load; case PIPE_ALGO_FEEDBACK: return (hash[counter % 100] < *drop_rate) ? -1 : 1; case PIPE_ALGO_HISTORY: return hist_check(pipe, 1); default: LM_ERR("ratelimit algorithm %d not implemented\n", pipe->algo); } return 1; }
int rl_get_counter_value(str *key) { unsigned int hash_idx; rl_pipe_t **pipe; int ret = -1; hash_idx = RL_GET_INDEX(*key); RL_GET_LOCK(hash_idx); /* try to get the value */ pipe = RL_FIND_PIPE(hash_idx, *key); if (!pipe || !*pipe) { LM_DBG("cannot find any pipe named %.*s\n", key->len, key->s); goto release; } if (RL_USE_CDB(*pipe)) { if (rl_get_counter(key, *pipe) < 0) { LM_ERR("cannot get the counter's value\n"); goto release; } } ret = rl_get_all_counters(*pipe); release: RL_RELEASE_LOCK(hash_idx); return ret; }
static int rl_map_print(void *param, str key, void *value) { struct mi_attr* attr; char* p; int len; struct rl_param_t * rl_param = (struct rl_param_t *)param; struct mi_node * rpl; rl_pipe_t *pipe = (rl_pipe_t *)value; struct mi_node * node; str *alg; if (!pipe) { LM_ERR("invalid pipe value\n"); return -1; } if (!rl_param || !rl_param->node || !rl_param->root) { LM_ERR("no reply node\n"); return -1; } rpl = rl_param->node; if (!key.len || !key.s) { LM_ERR("no key found\n"); return -1; } /* skip if no algo */ if (pipe->algo == PIPE_ALGO_NOP) return 0; if (!(node = add_mi_node_child(rpl, 0, "PIPE", 4, 0, 0))) return -1; if (!(attr = add_mi_attr(node, MI_DUP_VALUE, "id", 2, key.s, key.len))) return -1; if (!(alg = get_rl_algo_name(pipe->algo))) { LM_ERR("[BUG] unknown algorithm %d\n", pipe->algo); return -1; } if (!(attr = add_mi_attr(node, MI_DUP_VALUE, "algorithm", 9, alg->s, alg->len))) return -1; p = int2str((unsigned long)(pipe->limit), &len); if (!(attr = add_mi_attr(node, MI_DUP_VALUE, "limit", 5, p, len))) return -1; p = int2str((unsigned long)rl_get_all_counters(pipe), &len); if (!(attr = add_mi_attr(node, MI_DUP_VALUE, "counter", 7, p, len))) return -1; if ((++rl_param->counter % 50) == 0) { LM_DBG("flush mi tree - number %d\n", rl_param->counter); flush_mi_tree(rl_param->root); } return 0; }
/* timer housekeeping, invoked each timer interval to reset counters */ void rl_timer(unsigned int ticks, void *param) { unsigned int i = 0; map_iterator_t it, del; rl_pipe_t **pipe; str *key; void *value; unsigned long now = time(0); /* get CPU load */ if (get_cpuload() < 0) { LM_ERR("cannot update CPU load\n"); i = 1; } lock_get(rl_lock); /* if CPU was successfully loaded */ if (!i) do_update_load(); /* update network if needed */ if (*rl_network_count) *rl_network_load = get_total_bytes_waiting(PROTO_NONE); lock_release(rl_lock); /* iterate through each map */ for (i = 0; i < rl_htable.size; i++) { RL_GET_LOCK(i); /* iterate through all the entries */ if (map_first(rl_htable.maps[i], &it) < 0) { LM_ERR("map doesn't exist\n"); goto next_map; } for (; iterator_is_valid(&it);) { pipe = (rl_pipe_t **)iterator_val(&it); if (!pipe || !*pipe) { LM_ERR("[BUG] bogus map[%d] state\n", i); goto next_pipe; } key = iterator_key(&it); if (!key) { LM_ERR("cannot retrieve pipe key\n"); goto next_pipe; } /* check to see if it is expired */ if ((*pipe)->last_used + rl_expire_time < now) { /* this pipe is engaged in a transaction */ del = it; if (iterator_next(&it) < 0) LM_DBG("cannot find next iterator\n"); if ((*pipe)->algo == PIPE_ALGO_NETWORK) { lock_get(rl_lock); (*rl_network_count)--; lock_release(rl_lock); } LM_DBG("Deleting ratelimit pipe key \"%.*s\"\n", key->len, key->s); value = iterator_delete(&del); /* free resources */ if (value) shm_free(value); continue; } else { /* leave the lock if a cachedb query should be done*/ if (RL_USE_CDB(*pipe)) { if (rl_get_counter(key, *pipe) < 0) { LM_ERR("cannot get pipe counter\n"); goto next_pipe; } } switch ((*pipe)->algo) { case PIPE_ALGO_NETWORK: /* handle network algo */ (*pipe)->load = (*rl_network_load > (*pipe)->limit) ? -1 : 1; break; case PIPE_ALGO_RED: if ((*pipe)->limit && rl_timer_interval) (*pipe)->load = (*pipe)->counter / ((*pipe)->limit * rl_timer_interval); break; default: break; } (*pipe)->last_counter = rl_get_all_counters(*pipe); if (RL_USE_CDB(*pipe)) { if (rl_change_counter(key, *pipe, 0) < 0) { LM_ERR("cannot reset counter\n"); } } else { (*pipe)->counter = 0; } } next_pipe: if (iterator_next(&it) < 0) break; } next_map: RL_RELEASE_LOCK(i); } }
/** * 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, int update) { #define U2MILI(__usec__) (__usec__/1000) #define S2MILI(__sec__) (__sec__ *1000) int i; 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; pipe->counter = 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] += update; } 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] += update; } 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] += update; } 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] += update; } } /* count the total number of calls in the window */ for (i=0; i < pipe->rwin.window_size; i++) pipe->counter += pipe->rwin.window[i]; return pipe->counter > pipe->limit ? -1 : 1; #undef U2MILI #undef S2MILI }