mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-06 23:27:04 +02:00
[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:
parent
a3503e0b5a
commit
ae8b796722
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
114
src/acl.c
@ -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;
|
||||
|
@ -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 },
|
||||
}};
|
||||
|
||||
|
@ -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 },
|
||||
|
Loading…
Reference in New Issue
Block a user