[MEDIUM] smarter integer comparison support in ACLs

ACLs now support operators such as 'eq', 'le', 'lt', 'ge' and 'gt'
in order to give more flexibility to the language. Because of this
change, the 'dst_limit' keyword changed to 'dst_conn' and now requires
either a range or a test such as 'dst_conn lt 1000' which is more
understandable.
This commit is contained in:
Willy Tarreau 2007-06-09 23:10:04 +02:00
parent a3503e0b5a
commit ae8b796722
7 changed files with 167 additions and 81 deletions

View File

@ -2550,20 +2550,20 @@ It is possible to use the reserved keyword "OR" in conditions, and it is
possible for an acl to be specified multiple times, even with various tests, in
which case the first one which returns true validates the ACL.
As of 1.3.10, only the following tests have been implemented :
As of 1.3.12, only the following tests have been implemented :
Layer 3/4 :
src <ipv4_address>[/mask] ... : match IPv4 source address
dst <ipv4_address>[/mask] ... : match IPv4 destination address
src_port <low>[:<high>] ... : match source port range
dst_port <low>[:<high>] ... : match destination port range
dst_limit <max> : true if frontend has less than <max> connections
src_port <range> ... : match source port range
dst_port <range> ... : match destination port range
dst_conn <range> ... : match #connections on frontend
Layer 7 :
method <HTTP method> ... : match HTTP method
req_ver <1.0|1.1> ... : match HTTP request version
resp_ver <1.0|1.1> ... : match HTTP response version
status <low>[:<high>] ... : match HTTP response status code in range
status <range> ... : match HTTP response status code in range
url <string> ... : exact string match on URI
url_reg <regex> ... : regex string match on URI
url_beg <string> ... : true if URI begins with <string>
@ -2572,6 +2572,26 @@ As of 1.3.10, only the following tests have been implemented :
url_dir <string> ... : true if URI contains <string> between slashes
url_dom <string> ... : true if URI contains <string> between slashes or dots
A 'range' is one or two integers which may be prefixed by an operator.
The syntax is :
[<op>] <low>[:<high>]
Where <op> can be :
'eq' : the tested value must be equal to <low> or within <low>..<high>
'le' : the tested value must be lower than or equal to <low>
'lt' : the tested value must be lower than <low>
'ge' : the tested value must be greater than or equal to <low>
'gt' : the tested value must be greater than <low>
When no operator is defined, 'eq' is assumed. Note that when the operator is
specified, it applies to all subsequent ranges of values until the end of the
line is reached or another operator is specified. Example :
acl status_error status 400:599
acl saturated_frt dst_conn ge 1000
acl invalid_ports src_port lt 512 ge 65535
Other ones are coming (headers, cookies, time, auth), it's just a matter of
time. It is also planned to be able to read the patterns from a file, as well
as to ignore the case for some of them.

View File

@ -2652,20 +2652,20 @@ Il est
et il est possible pour une ACL d'être spécifiée plusieurs fois, même avec des
tests différents, auquel cas le premier test réussi validera l'ACL.
Au stade de la version 1.3.10, seuls les tests suivants ont été implémentés :
Au stade de la version 1.3.12, seuls les tests suivants ont été implémentés :
Niveaux 3/4 :
src <ipv4_address>[/mask] ... : match IPv4 source address
dst <ipv4_address>[/mask] ... : match IPv4 destination address
src_port <low>[:<high>] ... : match source port range
dst_port <low>[:<high>] ... : match destination port range
dst_limit <max> : true if frontend has less than <max> connections
src_port <range> ... : match source port range
dst_port <range> ... : match destination port range
dst_conn <range> ... : match #connections on frontend
Niveau 7 :
method <HTTP method> ... : match HTTP method
req_ver <1.0|1.1> ... : match HTTP request version
resp_ver <1.0|1.1> ... : match HTTP response version
status <low>[:<high>] ... : match HTTP response status code in range
status <range> ... : match HTTP response status code in range
url <string> ... : exact string match on URI
url_reg <regex> ... : regex string match on URI
url_beg <string> ... : true if URI begins with <string>
@ -2674,6 +2674,27 @@ Au stade de la version 1.3.10, seuls les tests suivants ont
url_dir <string> ... : true if URI contains <string> between slashes
url_dom <string> ... : true if URI contains <string> between slashes or dots
Une plage ('range') est constituée d'un ou deux entiers qui peuvent être
préfixés d'un opérateur. La syntaxe est :
[<op>] <min>[:<max>]
Avec <op> pouvant être :
'eq' : la valeur doit égaler <min> ou être comprise entre <min> et <max>
'le' : la valeur doit être inférieure ou égale à <min>
'lt' : la valeur doit être strictement inférieure à <min>
'ge' : la valeur doit être supérieure ou égale à <min>
'gt' : la valeur doit être strictement supérieure à <min>
Lorsqu'aucun opérateur n'est défini, 'eq' est employé. Noter que lorsqu'un
opérateur est spécifié, il s'applique à toutes les plages de valeurs suivantes
jusqu'à la fin de la ligne ou bien jusqu'à ce qu'un nouvel opérateur soit
précisé. Exemple :
acl status_error status 400:599
acl saturated_frt dst_conn ge 1000
acl invalid_ports src_port lt 512 ge 65535
D'autres tests arrivent (entêtes, cookies, heure, authentification), c'est
juste une question de temps. Il est aussi prévu de permettre de lire les
valeurs depuis un fichier, ainsi que d'ignorer la casse pour certains tests.

View File

@ -103,30 +103,28 @@ int acl_match_pst(struct acl_test *test, struct acl_pattern *pattern);
int acl_match_str(struct acl_test *test, struct acl_pattern *pattern);
/* Checks that the integer in <test> is included between min and max */
int acl_match_range(struct acl_test *test, struct acl_pattern *pattern);
int acl_match_min(struct acl_test *test, struct acl_pattern *pattern);
int acl_match_max(struct acl_test *test, struct acl_pattern *pattern);
int acl_match_int(struct acl_test *test, struct acl_pattern *pattern);
/* Parse an integer. It is put both in min and max. */
int acl_parse_int(const char *text, struct acl_pattern *pattern);
int acl_parse_int(const char **text, struct acl_pattern *pattern, int *opaque);
/* Parse a range of integers delimited by either ':' or '-'. If only one
* integer is read, it is set as both min and max.
*/
int acl_parse_range(const char *text, struct acl_pattern *pattern);
int acl_parse_range(const char **text, struct acl_pattern *pattern, int *opaque);
/* Parse a string. It is allocated and duplicated. */
int acl_parse_str(const char *text, struct acl_pattern *pattern);
int acl_parse_str(const char **text, struct acl_pattern *pattern, int *opaque);
/* Parse a regex. It is allocated. */
int acl_parse_reg(const char *text, struct acl_pattern *pattern);
int acl_parse_reg(const char **text, struct acl_pattern *pattern, int *opaque);
/* Parse an IP address and an optional mask in the form addr[/mask].
* The addr may either be an IPv4 address or a hostname. The mask
* may either be a dotted mask or a number of bits. Returns 1 if OK,
* otherwise 0.
*/
int acl_parse_ip(const char *text, struct acl_pattern *pattern);
int acl_parse_ip(const char **text, struct acl_pattern *pattern, int *opaque);
/* Checks that the pattern matches the end of the tested string. */
int acl_match_end(struct acl_test *test, struct acl_pattern *pattern);

View File

@ -73,7 +73,11 @@ struct acl_pattern {
struct list list; /* chaining */
union {
int i; /* integer value */
struct { int min, max; } range; /* integer range */
struct {
signed long long min, max;
int min_set :1;
int max_set :1;
} range; /* integer range */
struct {
struct in_addr addr;
struct in_addr mask;
@ -111,9 +115,18 @@ struct acl_test {
struct proxy;
struct session;
/*
* NOTE:
* The 'parse' function is called to parse words in the configuration. It must
* return the number of valid words read. 0 = error. The 'opaque' argument may
* be used by functions which need to maintain a context between consecutive
* values. It is initialized to zero before the first call, and passed along
* successive calls.
*/
struct acl_keyword {
const char *kw;
int (*parse)(const char *text, struct acl_pattern *pattern);
int (*parse)(const char **text, struct acl_pattern *pattern, int *opaque);
int (*fetch)(struct proxy *px, struct session *l4, void *l7, void *arg, struct acl_test *test);
int (*match)(struct acl_test *test, struct acl_pattern *pattern);
int use_cnt;

114
src/acl.c
View File

@ -10,6 +10,7 @@
*
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
@ -188,24 +189,10 @@ int acl_match_dom(struct acl_test *test, struct acl_pattern *pattern)
}
/* Checks that the integer in <test> is included between min and max */
int acl_match_range(struct acl_test *test, struct acl_pattern *pattern)
int acl_match_int(struct acl_test *test, struct acl_pattern *pattern)
{
if ((pattern->val.range.min <= test->i) &&
(test->i <= pattern->val.range.max))
return 1;
return 0;
}
int acl_match_min(struct acl_test *test, struct acl_pattern *pattern)
{
if (pattern->val.range.min <= test->i)
return 1;
return 0;
}
int acl_match_max(struct acl_test *test, struct acl_pattern *pattern)
{
if (test->i <= pattern->val.range.max)
if ((!pattern->val.range.min_set || pattern->val.range.min <= test->i) &&
(!pattern->val.range.max_set || test->i <= pattern->val.range.max))
return 1;
return 0;
}
@ -224,13 +211,12 @@ int acl_match_ip(struct acl_test *test, struct acl_pattern *pattern)
}
/* Parse a string. It is allocated and duplicated. */
int acl_parse_str(const char *text, struct acl_pattern *pattern)
int acl_parse_str(const char **text, struct acl_pattern *pattern, int *opaque)
{
int len;
len = strlen(text);
pattern->ptr.str = strdup(text);
len = strlen(*text);
pattern->ptr.str = strdup(*text);
if (!pattern->ptr.str)
return 0;
pattern->len = len;
@ -238,7 +224,7 @@ int acl_parse_str(const char *text, struct acl_pattern *pattern)
}
/* Parse a regex. It is allocated. */
int acl_parse_reg(const char *text, struct acl_pattern *pattern)
int acl_parse_reg(const char **text, struct acl_pattern *pattern, int *opaque)
{
regex_t *preg;
@ -247,7 +233,7 @@ int acl_parse_reg(const char *text, struct acl_pattern *pattern)
if (!preg)
return 0;
if (regcomp(preg, text, REG_EXTENDED | REG_NOSUB) != 0) {
if (regcomp(preg, *text, REG_EXTENDED | REG_NOSUB) != 0) {
free(preg);
return 0;
}
@ -256,23 +242,40 @@ int acl_parse_reg(const char *text, struct acl_pattern *pattern)
return 1;
}
/* Parse an integer. It is put both in min and max. */
int acl_parse_int(const char *text, struct acl_pattern *pattern)
{
pattern->val.range.min = pattern->val.range.max = __str2ui(text);
return 1;
}
/* Parse a range of integers delimited by either ':' or '-'. If only one
* integer is read, it is set as both min and max.
/* Parse a range of positive integers delimited by either ':' or '-'. If only
* one integer is read, it is set as both min and max. An operator may be
* specified as the prefix, among this list of 5 :
*
* 0:eq, 1:gt, 2:ge, 3:lt, 4:le
*
* The default operator is "eq". It supports range matching. Ranges are
* rejected for other operators. The operator may be changed at any time.
* The operator is stored in the 'opaque' argument.
*
*/
int acl_parse_range(const char *text, struct acl_pattern *pattern)
int acl_parse_int(const char **text, struct acl_pattern *pattern, int *opaque)
{
unsigned int i, j, last;
signed long long i;
unsigned int j, last, skip = 0;
const char *ptr = *text;
while (!isdigit(*ptr)) {
if (strcmp(ptr, "eq") == 0) *opaque = 0;
else if (strcmp(ptr, "gt") == 0) *opaque = 1;
else if (strcmp(ptr, "ge") == 0) *opaque = 2;
else if (strcmp(ptr, "lt") == 0) *opaque = 3;
else if (strcmp(ptr, "le") == 0) *opaque = 4;
else
return 0;
skip++;
ptr = text[skip];
}
last = i = 0;
while (1) {
j = (*text++);
j = *ptr++;
if ((j == '-' || j == ':') && !last) {
last++;
pattern->val.range.min = i;
@ -286,10 +289,34 @@ int acl_parse_range(const char *text, struct acl_pattern *pattern)
i *= 10;
i += j;
}
if (last && *opaque >= 1 && *opaque <= 4)
/* having a range with a min or a max is absurd */
return 0;
if (!last)
pattern->val.range.min = i;
pattern->val.range.max = i;
return 1;
switch (*opaque) {
case 0: /* eq */
pattern->val.range.min_set = 1;
pattern->val.range.max_set = 1;
break;
case 1: /* gt */
pattern->val.range.min++; /* gt = ge + 1 */
case 2: /* ge */
pattern->val.range.min_set = 1;
pattern->val.range.max_set = 0;
break;
case 3: /* lt */
pattern->val.range.max--; /* lt = le - 1 */
case 4: /* le */
pattern->val.range.min_set = 0;
pattern->val.range.max_set = 1;
break;
}
return skip + 1;
}
/* Parse an IP address and an optional mask in the form addr[/mask].
@ -297,9 +324,12 @@ int acl_parse_range(const char *text, struct acl_pattern *pattern)
* may either be a dotted mask or a number of bits. Returns 1 if OK,
* otherwise 0.
*/
int acl_parse_ip(const char *text, struct acl_pattern *pattern)
int acl_parse_ip(const char **text, struct acl_pattern *pattern, int *opaque)
{
return str2net(text, &pattern->val.ipv4.addr, &pattern->val.ipv4.mask);
if (str2net(*text, &pattern->val.ipv4.addr, &pattern->val.ipv4.mask))
return 1;
else
return 0;
}
/*
@ -390,6 +420,7 @@ struct acl_expr *parse_acl_expr(const char **args)
struct acl_expr *expr;
struct acl_keyword *aclkw;
struct acl_pattern *pattern;
int opaque;
const char *arg;
aclkw = find_acl_kw(args[0]);
@ -423,14 +454,17 @@ struct acl_expr *parse_acl_expr(const char **args)
/* now parse all patterns */
args++;
opaque = 0;
while (**args) {
int ret;
pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern));
if (!pattern)
goto out_free_expr;
if (!aclkw->parse(*args, pattern))
ret = aclkw->parse(args, pattern, &opaque);
if (!ret)
goto out_free_pattern;
LIST_ADDQ(&expr->patterns, &pattern->list);
args++;
args += ret;
}
return expr;

View File

@ -517,14 +517,14 @@ static int acl_fetch_dconn(struct proxy *px, struct session *l4, void *l7, void
/* Note: must not be declared <const> as its list will be overwritten */
static struct acl_kw_list acl_kws = {{ },{
{ "src_port", acl_parse_range, acl_fetch_sport, acl_match_range },
{ "src_port", acl_parse_int, acl_fetch_sport, acl_match_int },
{ "src", acl_parse_ip, acl_fetch_src, acl_match_ip },
{ "dst", acl_parse_ip, acl_fetch_dst, acl_match_ip },
{ "dst_port", acl_parse_range, acl_fetch_dport, acl_match_range },
{ "dst_port", acl_parse_int, acl_fetch_dport, acl_match_int },
#if 0
{ "src_limit", acl_parse_int, acl_fetch_sconn, acl_match_max },
{ "src_limit", acl_parse_int, acl_fetch_sconn, acl_match_int },
#endif
{ "dst_limit", acl_parse_int, acl_fetch_dconn, acl_match_max },
{ "dst_conn", acl_parse_int, acl_fetch_dconn, acl_match_int },
{ NULL, NULL, NULL, NULL },
}};

View File

@ -5147,16 +5147,16 @@ void debug_hdr(const char *dir, struct session *t, const char *start, const char
* We use the pre-parsed method if it is known, and store its number as an
* integer. If it is unknown, we use the pointer and the length.
*/
static int acl_parse_meth(const char *text, struct acl_pattern *pattern)
static int acl_parse_meth(const char **text, struct acl_pattern *pattern, int *opaque)
{
int len, meth;
len = strlen(text);
meth = find_http_meth(text, len);
len = strlen(*text);
meth = find_http_meth(*text, len);
pattern->val.i = meth;
if (meth == HTTP_METH_OTHER) {
pattern->ptr.str = strdup(text);
pattern->ptr.str = strdup(*text);
if (!pattern->ptr.str)
return 0;
pattern->len = len;
@ -5198,12 +5198,12 @@ static int acl_match_meth(struct acl_test *test, struct acl_pattern *pattern)
/* 2. Check on Request/Status Version
* We simply compare strings here.
*/
static int acl_parse_ver(const char *text, struct acl_pattern *pattern)
static int acl_parse_ver(const char **text, struct acl_pattern *pattern, int *opaque)
{
pattern->ptr.str = strdup(text);
pattern->ptr.str = strdup(*text);
if (!pattern->ptr.str)
return 0;
pattern->len = strlen(text);
pattern->len = strlen(*text);
return 1;
}
@ -5286,7 +5286,7 @@ static struct acl_kw_list acl_kws = {{ },{
{ "method", acl_parse_meth, acl_fetch_meth, acl_match_meth },
{ "req_ver", acl_parse_ver, acl_fetch_rqver, acl_match_str },
{ "resp_ver", acl_parse_ver, acl_fetch_stver, acl_match_str },
{ "status", acl_parse_range, acl_fetch_stcode, acl_match_range },
{ "status", acl_parse_int, acl_fetch_stcode, acl_match_int },
{ "url", acl_parse_str, acl_fetch_url, acl_match_str },
{ "url_beg", acl_parse_str, acl_fetch_url, acl_match_beg },