diff --git a/README.rst b/README.rst index dba44ae..4b4a820 100644 --- a/README.rst +++ b/README.rst @@ -19,14 +19,25 @@ import throttle; DESCRIPTION =========== -This vmod most obvious uses are to handle denial of services by a single user (or bot) punching through the cache, or to set rate limits to API calls you provide. - -It will allow you to set rate limiting, on several different time windows, per path/IP/whatever you want. If a time window limit was reached, this vmod will return you the time to wait before this call will be authorized. -With this return information, you can handle rate limit overflow by either abruptly returning an error, or by actually waiting the necessary time if you deems it reasonable, for a smoother rate limit overflow handling. - -Please note that at the moment, there is no native way (AFAIK) to wait within Varnish (and no, using sleep() in the VCL is a *bad* idea), so you'll have to hack your way to it, e.g. redirect to a separate server that will wait for you and come back later. - -Please read carefully the "Memory management and denial of service" section below to avoid misuses that could lead to denial of services. +This vmod most obvious uses are to handle denial of services by a +single user (or bot) punching through the cache, or to set rate limits +to API calls you provide. + +It will allow you to set rate limiting, on several different time +windows, per path/IP/whatever you want. If a time window limit was +reached, this vmod will return you the time to wait before this call +will be authorized. With this return information, you can handle rate +limit overflow by either abruptly returning an error, or by actually +waiting the necessary time if you deems it reasonable, for a smoother +rate limit overflow handling. + +Please note that at the moment, there is no native way (AFAIK) to wait +within Varnish (and no, using sleep() in the VCL is a *bad* idea), so +you'll have to hack your way to it, e.g. redirect to a separate server +that will wait for you and come back later. + +Please read carefully the "Memory management and denial of service" +section below to avoid misuses that could lead to denial of services. "This is far better than any result I've seen from either HAProxy or Nginx doing layer 7 inspection on such modest [test] hardware." @@ -45,14 +56,23 @@ Prototype Arguments key: A unique key that will identify what you are throttling. Can be used in may ways. See the examples below. - rate_limits: A list of different rate limits you want to put on this call. The syntax is the following: "[nb_of_calls]req/[duration][durations_size], ...". Example: "3req/s, 10req/30s, 30req/5m, 100req/h". Please note that we are using the Varnish duration size identifiers: s, m (and not mn/min), h, d. WARNING: You cannot define different rate_limits for a same key. If you do, only the one given in the first call will be used. See API rate limiting example below. + rate_limits: A list of different rate limits you want to put on + this call. The syntax is the following: + "[nb_of_calls]req/[duration][durations_size], ...". Example: + "3req/s, 10req/30s, 30req/5m, 100req/h". Please note that we are + using the Varnish duration size identifiers: s, m (and not + mn/min), h, d. WARNING: You cannot define different rate_limits + for a same key. If you do, only the one given in the first call + will be used. See API rate limiting example below. Return value DURATION Description Increments the call counters. Returns 0.0 if the call was authorized, or the time to wait if one of the time window limit was reached. Example - Prevent a single user (or crazy googlebot) to make a denial of service by punching through the cache: limit MISS calls on non-static assets by IP, max 3 req/s, 10 req/30s, 30 req/5m:: + Prevent a single user (or crazy googlebot) to make a denial of + service by punching through the cache: limit MISS calls on + non-static assets by IP, max 3 req/s, 10 req/30s, 30 req/5m:: sub vcl_miss { if(req.url !~ "\.(jpg|jpeg|png|gif|ico|swf|css|js|html|htm)$") { @@ -82,6 +102,27 @@ Example Please note: You cannot set 2 differents set of rate limits for a same key. (If you do, only one will be used, and the other will be ignored). In this example, simply add some extra text to the key to differentiate the authentificated calls from the non-authentificated ones. +set_windows +----------- + +Prototype + :: + + set_windows(STRING key, STRING rate_limits) +Arguments + key: A unique key that will identify what you are throttling. + + rate_limits: A list of different rate limits you want to put on + this call. + + These arguments have the same meaning like in is_allowed. +Return value + VOID +Description + Create a set of named windows under the given key. The rule set + is marked as being an "explicit" rule, which will enable more + logging for it. It is intended to be used from vcl_init or as + part of an internal handler to create global rules. remaining_calls --------------- @@ -118,36 +159,72 @@ Example MEMORY MANAGEMENT AND DENIAL OF SERVICE ======================================= -If used incorrectly, this tool could let an attacker force Varnish to consume all available memory and crash. It would be too bad to be DoS'ed by a tool that prevents DoS! -What you need to know is that this vmod will keep in memory the time of the revelant last requests for each key you provide. And this memory is *outside* of the memory you specify to Varnish for caching. (So if you specify 4G of RAM to varnish, this vmod memory will be on top of it.) +If used incorrectly, this tool could let an attacker force Varnish to +consume all available memory and crash. It would be too bad to be +DoS'ed by a tool that prevents DoS! What you need to know is that +this vmod will keep in memory the time of the revelant last requests +for each key you provide. And this memory is *outside* of the memory +you specify to Varnish for caching. (So if you specify 4G of RAM to +varnish, this vmod memory will be on top of it.) -For a given key, the amount of necessary memory is at its maximum fixed to the maximum number of request limit you give to this key, multiplied by 16 bytes. For example:: +For a given key, the amount of necessary memory is at its maximum +fixed to the maximum number of request limit you give to this key, +multiplied by the size of one the internal data structure to keep +track of a key, currently 56 bytes on a 64 bit system. For example:: if(throttle.is_allowed("pouet", "2req/s, 100req/h, 1000req/d") > 0s) -For the key "pouet", the maximum memory usage will be 1000 (the maximum number between 2, 100, and 1000) multiplied by 16 bytes = 16 kbytes. Now, with a more advanced key:: +For the key "pouet", the maximum memory usage will be 1000 (the +maximum number between 2, 100, and 1000) multiplied by 56 bytes ~= 56 +kbytes. Now, with a more advanced key:: if(throttle.is_allowed("ip:" + client.ip, "2req/s, 100req/3h, 1000req/d") > 0s) -We now have one key per client IP, which will each consume 16kbytes maximum. That is potentially unlimited. So what you also need to know is that the request times are kept in memory until they get older than the biggest time window: here one day (the biggest between 1s, 3 hours and 1 day). -So if you take an average of 10,000 differents IP per day, that would cost at the maximum (if every IP was making 1000 calls), 10,000 * 16kbytes = 160 mbytes. That begins to be quite a number. So one can reduce this number by keeping request limits lower. For example:: +We now have one key per client IP, which will each consume ~56kbytes +maximum. That is potentially unlimited. So what you also need to know +is that the request times are kept in memory until they get older than +the biggest time window: here one day (the biggest between 1s, 3 hours +and 1 day). So if you take an average of 10,000 differents IP per +day, that would cost at the maximum (if every IP was making 1000 +calls), 10,000 * 56kbytes ~= 560 mbytes. That begins to be quite a +number. So one can reduce this number by keeping request limits +lower. For example:: if(throttle.is_allowed("ip:" + client.ip, "2req/s, 30req/h") > 0s) -This would reduce the maximum memory consumption, with 10,000 differents IP per day, to 10,000 * 30 * 16 = 4.8 mbytes. Much better. But wait! Now that we no longer have the 1 day window, the request times will only be kept for the new largest window, 1 hour. So if we have around 1,000 different IP per hour, that makes a maximum memory consumption of 1,000 * 30 * 16 = 480 kbytes. Muuch better! So we see that the time window sizes and lengths has a big impact on memory consumption. - -With the following example, we are theorically still open to distributed denial of service due to this vmod, but with the required number of necessary clients to consume all memory, it is much more likely that your backend services will fall and crash first. (And remember, we only use at maximum a fixed amount of memory per key, whatever the number of calls for this key). - -When we begin to be vulnerable to denial of service by a single user is when a single user can have an unlimited number of keys:: +This would reduce the maximum memory consumption, with 10,000 +differents IP per day, to 10,000 * 30 * 56 ~= 16.8 mbytes. Much +better. But wait! Now that we no longer have the 1 day window, the +request times will only be kept for the new largest window, 1 hour. So +if we have around 1,000 different IP per hour, that makes a maximum +memory consumption of 1,000 * 30 * 56 ~= 1,7 mbytes. Muuch better! So +we see that the time window sizes and lengths has a big impact on +memory consumption. + +With the following example, we are theorically still open to +distributed denial of service due to this vmod, but with the required +number of necessary clients to consume all memory, it is much more +likely that your backend services will fall and crash first. (And +remember, we only use at maximum a fixed amount of memory per key, +whatever the number of calls for this key). + +When we begin to be vulnerable to denial of service by a single user +is when a single user can have an unlimited number of keys:: if(throttle.is_allowed("ip:" + client.ip + ":path:" + req.url, "2req/s, 30req/h") > 0s) -With this example, you would limit the request rate per IP and per URL. A single user can thus create an unlimited number of keys, and thus consume an unlimited amount of memory, and make a denial of service by crashing varnish. So if you are in a case when you want to have different rate limits per path, it is a good idea to normalize the paths, and have a limited number of them only. For example:: +With this example, you would limit the request rate per IP and per +URL. A single user can thus create an unlimited number of keys, and +thus consume an unlimited amount of memory, and make a denial of +service by crashing varnish. So if you are in a case when you want to +have different rate limits per path, it is a good idea to normalize +the paths, and have a limited number of them only. For example:: if(req.url ~ "^/my_api_path/my_api_name") { if(throttle.is_allowed("ip:" + client.ip + ":api:api_name", "2req/s, 30req/h") > 0s) -Finally, if you want to want to track the memory usage of this throttle vmod , you can use this command:: +Finally, if you want to want to track the memory usage of this +throttle vmod , you can use this command:: if(req.url == "/my_admin_page") { set resp.http.X-throttle-memusage = throttle.memory_usage(); diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..d1c2300 --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,4 @@ +files +libvmod-throttle +*substvars +*log diff --git a/debian/rules b/debian/rules index 5694512..061b520 100755 --- a/debian/rules +++ b/debian/rules @@ -1,9 +1,9 @@ #!/usr/bin/make -f export DH_VERBOSE=1 -VARNISHSRC = $(DEBIAN_VARNISH_SRC) -VMODDIR = $(shell PKG_CONFIG_PATH="$(VARNISHSRC)" pkg-config --variable=vmoddir varnishapi) -VMOD_ABI = $(shell printf '\#include "vmod_abi.h"\nVMOD_ABI_Version' | cpp - -I$(DEBIAN_VARNISH_SRC)/include | sed '/^\#/D;s/"//g;s/\([A-Z]\)/\L\1/g;s/[^a-z0-9.]/-/g;s/varnish/varnishabi/') +VARNISHSRC ?= $(DEBIAN_VARNISH_SRC) +VMODDIR ?= $(shell PKG_CONFIG_PATH="$(VARNISHSRC)" pkg-config --variable=vmoddir varnishapi) +VMOD_ABI ?= $(shell printf '\#include "vmod_abi.h"\nVMOD_ABI_Version' | cpp - -I$(DEBIAN_VARNISH_SRC)/include | sed '/^\#/D;s/"//g;s/\([A-Z]\)/\L\1/g;s/[^a-z0-9.]/-/g;s/varnish/varnishabi/') override_dh_auto_configure: dh_auto_configure -- VMODDIR="$(VMODDIR)" VARNISHSRC="$(VARNISHSRC)" @@ -14,7 +14,7 @@ override_dh_gencontrol: if [ -n "$$DEBIAN_OVERRIDE_BINARY_VERSION" ]; then \ dh_gencontrol -- -Tdebian/substvars -v$$DEBIAN_OVERRIDE_BINARY_VERSION; \ else \ - dh_gencontrol -Tdebian/substvars; \ + dh_gencontrol -- -Tdebian/substvars; \ fi %: diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..99fa377 --- /dev/null +++ b/package.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -x + +export VARNISH_VERSION=${VARNISH_VERSION:="3.0.5"} +export VARNISHSRC=../varnish-${VARNISH_VERSION} +export DEBIAN_VARNISH_SRC=$VARNISHSRC +export VMODDIR=/usr/lib/varnish/vmods + +git submodule update --init +./autogen.sh && ./configure && make +dpkg-buildpackage -uc -us -b diff --git a/src/vmod_throttle.c b/src/vmod_throttle.c index b2fb545..8d03f6a 100644 --- a/src/vmod_throttle.c +++ b/src/vmod_throttle.c @@ -1,11 +1,18 @@ #include #include +#include #include "vrt.h" #include "bin/varnishd/cache.h" #include "vcc_if.h" +enum rule_type { + NO_CREATE = 0, + AUTO_RULE, + EXPLICIT_RULE +}; + //Individual call struct vmodth_call { //Call time @@ -30,12 +37,16 @@ struct vmodth_call_win { //Call set, identified by a key struct vmodth_calls { + //Def: Type of the rule + enum rule_type rule_type; //Def: Key - char* key; + const char* key; + //Def: Unparsed Limit windows + const char* window_limits; //Def: Limit windows struct vmodth_call_win* wins; //Def: Nb of windows - int nb_wins; + short nb_wins; //Status: Linked list, first call struct vmodth_call* first; //Status: Linked list, last call @@ -60,10 +71,41 @@ pthread_rwlock_t vmodth_rwlock; #define LOCK_WRITE() assert(pthread_rwlock_wrlock(&vmodth_rwlock) == 0); #define UNLOCK() pthread_rwlock_unlock(&vmodth_rwlock); +// Private: Free one calls structure including all dependents +void +_vmod_free_calls(struct vmodth_calls *calls) { + struct vmodth_call *call = calls->last; + while(call) { + struct vmodth_call *prev = call->prev; + free(call); + call = prev; + } + free((void*) calls->key); + free((void*) calls->window_limits); + free(calls->wins); + free(calls); +} + +// Private: Free all memory +void +_vmod_free_all(void* data) { + //Seek and destroy. Mwahahahahh + struct vmodth_priv *priv = ((struct vmodth_priv*)data); + + for(int i = 0; i < 4096; i++) { + struct vmodth_calls *calls = priv->hashmap[i]; + while(calls) { + struct vmodth_calls *next = calls->next; + _vmod_free_calls(calls); + calls = next; + } + } + free(data); +} // Private: the djb2 string hash algorithm unsigned long -_vmod_hash(unsigned char *str) { +_vmod_hash(const unsigned char *str) { unsigned long hash = 5381; int c; @@ -76,86 +118,83 @@ _vmod_hash(unsigned char *str) { // Private: Parse a window limit list, return the current window limit, put the cursor at // the beginning of the next one. // A result window with a length of 0 means it failed to parse. -struct vmodth_call_win _vmod_parse_win(char** wins) { - struct vmodth_call_win result; - int parsed_max = 0; - int parsed_length = 0; - char c; - - //Init the result win - result.length = 0; - result.max_calls = 0; - result.nb_calls = 0; - result.last_call = NULL; - - if(c = *(*wins)++) { - //Get rid of any whitespaces - while(c == ' ') { - c = *(*wins)++; - } - - //Expecting a number - while(48 <= c && c <= 57) { - parsed_max = parsed_max*10 + (c - 48); - c = *(*wins)++; - } - if(parsed_max == 0) { - return result; - } +struct vmodth_call_win _vmod_parse_win(const char** wins) { + struct vmodth_call_win result; + int parsed_max = 0; + int parsed_length = 0; + char c; + + //Init the result win + result.length = 0; + result.max_calls = 0; + result.nb_calls = 0; + result.last_call = NULL; + + if(c = *(*wins)++) { + //Get rid of any whitespaces + while(c == ' ') { + c = *(*wins)++; + } - //Get rid of any whitespaces - while(c == ' ') { - c = *(*wins)++; - } + //Expecting a number + while(48 <= c && c <= 57) { + parsed_max = parsed_max*10 + (c - 48); + c = *(*wins)++; + } + if(parsed_max == 0) { + return result; + } - //Expecting the string "req/" - if(strncmp(--(*wins), "req/", 4) != 0) { - return result; - } - (*wins) += 4; + //Get rid of any whitespaces + while(c == ' ') { c = *(*wins)++; + } - //Expecting either a number and a duration qualitifer (s,m,h,d), or a duration qualitifer - while(48 <= c && c <= 57) { - parsed_length = parsed_length*10 + (c - 48); - c = *(*wins)++; - } - if(parsed_length == 0) { - parsed_length = 1; - } - if(c == 'm') { - parsed_length *= 60; - } - else if(c == 'h') { - parsed_length *= 3600; - } - else if(c == 'd') { - parsed_length *= 24*3600; - } + //Expecting the string "req/" + if(strncmp(--(*wins), "req/", 4) != 0) { + return result; + } + (*wins) += 4; + c = *(*wins)++; - //Then expecting the next char to be either NULL or the ',' separator + //Expecting either a number and a duration qualitifer (s,m,h,d), or a duration qualitifer + while(48 <= c && c <= 57) { + parsed_length = parsed_length*10 + (c - 48); c = *(*wins)++; - if(c != 0 && c != ',') { - return result; - } + } + if(parsed_length == 0) { + parsed_length = 1; + } + if(c == 'm') { + parsed_length *= 60; + } + else if(c == 'h') { + parsed_length *= 3600; + } + else if(c == 'd') { + parsed_length *= 24*3600; + } - //Now let's create the new win struct - result.length = parsed_length; - result.max_calls = parsed_max; - result.nb_calls = 0; - result.last_call = NULL; + //Then expecting the next char to be either NULL or the ',' separator + c = *(*wins)++; + if(c != 0 && c != ',') { + return result; } - return result; + //Now let's create the new win struct + result.length = parsed_length; + result.max_calls = parsed_max; + result.nb_calls = 0; + result.last_call = NULL; + } + + return result; } // Private: Fetch or create the call set from the given key -struct vmodth_calls* _vmod_get_call_set_from_key(struct vmodth_priv* priv, char* key, int create_if_not_existing, char* window_limits) { +struct vmodth_calls* _vmod_get_call_set_from_key(struct vmodth_priv* priv, const char* key, enum rule_type rule_type, const char* window_limits) { struct vmodth_calls* result = NULL; - int hash_key; - - //Get the hash key from the key - hash_key = _vmod_hash(key) & 0xfff; + int hash_key = hash_key = _vmod_hash(key) & 0xfff; //Search in the hash bucket struct vmodth_calls* cur = priv->hashmap[hash_key]; @@ -167,53 +206,77 @@ struct vmodth_calls* _vmod_get_call_set_from_key(struct vmodth_priv* priv, char* cur = cur->next; } - //If not found, and we are allowed to create, we have to create this new call set - if(!result && create_if_not_existing) { - //First we do the parsing of the windows. If it fails, we will stop. + if (rule_type != NO_CREATE) { + int changed_window_limits = !result || strcmp(result->window_limits, window_limits); struct vmodth_call_win* parsed_wins = NULL; int parsed_win_count = 0; - struct vmodth_call_win parsed_win; - //We parse windows until either we reached the end of string, or a parsing failed. - while(*window_limits) { - //Parse a window - parsed_win = _vmod_parse_win(&window_limits); - - if(!parsed_win.length) { - //Parsing failed. Quit. - break; + if (changed_window_limits) { + //Parse the window limits string + const char *window_limits_p = window_limits; + + //We parse windows until either we reached the end of string, or a parsing failed. + while(*window_limits_p) { + //Parse a window + struct vmodth_call_win parsed_win = _vmod_parse_win(&window_limits_p); + + if(!parsed_win.length) { + //Parsing failed. Quit. + break; + } + + //Now let's copy the result in the window list + parsed_win_count++; + if(parsed_win_count == 1) { + parsed_wins = malloc(sizeof(struct vmodth_call_win)); + } + else { + parsed_wins = realloc(parsed_wins, sizeof(struct vmodth_call_win) * parsed_win_count); + } + memcpy(parsed_wins + (parsed_win_count - 1), &parsed_win, sizeof(struct vmodth_call_win)); } - //Now let's copy the result in the window list - parsed_win_count++; - if(parsed_win_count == 1) { - parsed_wins = malloc(sizeof(struct vmodth_call_win)); + //If we failed to parse any window, return unmodified entry (may be NULL) + if(parsed_win_count == 0) { + free(parsed_wins); + if (result) { + syslog(LOG_ERR, "invalid window limits string \"%s\" for \"%s\", limits not changed from \"%s\"", window_limits, key, result->window_limits); + } else { + syslog(LOG_ERR, "invalid window limits string \"%s\" for \"%s\", limits not set", window_limits, key); + } + return result; } - else { - parsed_wins = realloc(parsed_wins, sizeof(struct vmodth_call_win) * parsed_win_count); + + if(result && (result->rule_type == EXPLICIT_RULE) && changed_window_limits) { + syslog(LOG_INFO, "changed window limits for key \"%s\" from \"%s\" to \"%s\"", key, result->window_limits, window_limits); + } else if(!result && (rule_type == EXPLICIT_RULE)) { + syslog(LOG_INFO, "new window limits for key \"%s\": \"%s\"", key, window_limits); } - memcpy(parsed_wins + (parsed_win_count - 1), &parsed_win, sizeof(struct vmodth_call_win)); - } - //If we failed to parse any window, return NULL - if(parsed_win_count == 0) { - return result; + if(result) { + free((void*) result->window_limits); + result->nb_wins = 0; + free(result->wins); + result->wins = 0; + } else { + result = malloc(sizeof(struct vmodth_calls)); + AN(result); + result->key = strdup(key); + result->rule_type = rule_type; + + result->first = NULL; + result->last = NULL; + result->nb_calls = 0; + + //Place it, in first position + result->next = priv->hashmap[hash_key]; + priv->hashmap[hash_key] = result; + } + + result->window_limits = strdup(window_limits); + result->nb_wins = parsed_win_count; + result->wins = parsed_wins; } - - //Create the actual vmodth_calls structure - result = malloc(sizeof(struct vmodth_calls)); - AN(result); - result->key = malloc(strlen(key) + 1); - strncpy(result->key, key, strlen(key) + 1); - result->first = NULL; - result->last = NULL; - result->nb_calls = 0; - result->nb_wins = parsed_win_count; - result->wins = parsed_wins; - - //Place it, in first position - result->next = priv->hashmap[hash_key]; - priv->hashmap[hash_key] = result; } return result; @@ -268,7 +331,7 @@ _vmod_remove_older_entries(struct vmodth_calls* calls, double now) { } while(calls->last && - (calls->nb_calls > max_win_max_calls || calls->last->time < now - max_win_length)) { + (calls->nb_calls > max_win_max_calls || calls->last->time < now - max_win_length)) { prev = calls->last->prev; free(calls->last); calls->last = prev; @@ -298,7 +361,7 @@ _vmod_garbage_collector(struct vmodth_priv* priv, int hashmap_slot, double now) //Then update the list of vmodth_call _vmod_remove_older_entries(calls, now); - + //Finally, if there is no longer any vmodth_call, delete this vmodth_calls, and goto next if(calls->last == NULL) { if(prev_calls == NULL) { @@ -319,43 +382,13 @@ _vmod_garbage_collector(struct vmodth_priv* priv, int hashmap_slot, double now) } } -// Private: Free all memory -void -_vmod_free_all(void* data) { - struct vmodth_priv *priv; - struct vmodth_calls *calls; - struct vmodth_call *call; - - //Seek and destroy. Mwahahahahh - priv = ((struct vmodth_priv*)data); - for(int i = 0; i < 4096; i++) { - calls = priv->hashmap[i]; - while(calls) { - //Free call entries - call = calls->last; - while(call) { - calls->last = call->prev; - free(call); - call = calls->last; - } - - //Free wins - free(calls->wins); - - //Free calls itself - priv->hashmap[i] = calls->next; - free(calls); - calls = priv->hashmap[i]; - } - } - free(data); -} - // Public: Vmod init function, initialize the data structure int init_function(struct vmod_priv *pc, const struct VCL_conf *conf) { struct vmodth_priv *priv; + openlog("libvmod-throttle", LOG_PID, LOG_LOCAL0); + //Init the rwlock pthread_rwlock_init(&vmodth_rwlock, NULL); @@ -379,20 +412,19 @@ init_function(struct vmod_priv *pc, const struct VCL_conf *conf) { // Public: is_allowed VCL command double vmod_is_allowed(struct sess *sp, struct vmod_priv *pc, const char* key, const char* window_limits) { - struct vmodth_priv *priv; + struct vmodth_priv *priv = ((struct vmodth_priv*)pc->priv); struct vmodth_calls *calls; - double result = 0; + double result = 0; - //Our persistent data structure - priv = ((struct vmodth_priv*)pc->priv); AN(priv); LOCK_WRITE(); //Get the call set for this given key - calls = _vmod_get_call_set_from_key(priv, key, 1, window_limits); + calls = _vmod_get_call_set_from_key(priv, key, AUTO_RULE, window_limits); //calls can be NULL if the parsing of the windows failed if(calls == NULL) { + syslog(LOG_ERR, "could not create or re-set key \"%s\"", key); UNLOCK(); return -1.0; } @@ -460,11 +492,21 @@ vmod_is_allowed(struct sess *sp, struct vmod_priv *pc, const char* key, const ch return result; } -// Public: is_allowed VCL command +// Public: set_windows VCL command - This just sets the limit for a key +void +vmod_set_windows(struct sess *sp, struct vmod_priv *pc, const char* key, const char* window_limits) { + struct vmodth_priv *priv = ((struct vmodth_priv*)pc->priv); + + LOCK_WRITE(); + (void) _vmod_get_call_set_from_key(priv, key, EXPLICIT_RULE, window_limits); + UNLOCK(); +} + +// Public: remaining_calls VCL command int vmod_remaining_calls(struct sess *sp, struct vmod_priv *pc, const char* key, const char* window_limit) { int result = -1; - char* window_limit_str; + const char* window_limit_str; struct vmodth_priv *priv; struct vmodth_calls *calls; struct vmodth_call_win win; @@ -476,9 +518,9 @@ vmod_remaining_calls(struct sess *sp, struct vmod_priv *pc, const char* key, con AN(priv); //Get the call set for this given key - calls = _vmod_get_call_set_from_key(priv, key, 0, NULL); + calls = _vmod_get_call_set_from_key(priv, key, NO_CREATE, NULL); if(calls == NULL) { - //calls not found, we return an error (-1) + syslog(LOG_ERR, "could not get key \"%s\"", key); UNLOCK(); return result; } @@ -538,4 +580,4 @@ vmod_memory_usage(struct sess *sp, struct vmod_priv *pc) { UNLOCK(); return result; -} \ No newline at end of file +} diff --git a/src/vmod_throttle.vcc b/src/vmod_throttle.vcc index cd404ef..e745ec3 100644 --- a/src/vmod_throttle.vcc +++ b/src/vmod_throttle.vcc @@ -1,5 +1,6 @@ Module throttle Init init_function Function DURATION is_allowed(PRIV_VCL, STRING, STRING) +Function VOID set_windows(PRIV_VCL, STRING, STRING) Function INT remaining_calls(PRIV_VCL, STRING, STRING) -Function INT memory_usage(PRIV_VCL) \ No newline at end of file +Function INT memory_usage(PRIV_VCL)