[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.
diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt
index 73372e3..2a278b0 100644
--- a/doc/haproxy-en.txt
+++ b/doc/haproxy-en.txt
@@ -2550,20 +2550,20 @@
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 @@
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.
diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt
index ec74611..7d6ebf1 100644
--- a/doc/haproxy-fr.txt
+++ b/doc/haproxy-fr.txt
@@ -2652,20 +2652,20 @@
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 @@
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.
diff --git a/include/proto/acl.h b/include/proto/acl.h
index ace72dd..59c2139 100644
--- a/include/proto/acl.h
+++ b/include/proto/acl.h
@@ -103,30 +103,28 @@
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);
diff --git a/include/types/acl.h b/include/types/acl.h
index 31f30fa..02c1258 100644
--- a/include/types/acl.h
+++ b/include/types/acl.h
@@ -73,7 +73,11 @@
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 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;
diff --git a/src/acl.c b/src/acl.c
index 1dce3ea..efa7853 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -10,6 +10,7 @@
*
*/
+#include <ctype.h>
#include <stdio.h>
#include <string.h>
@@ -188,28 +189,14 @@
}
/* 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))
+ 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;
}
-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)
- return 1;
- return 0;
-}
-
int acl_match_ip(struct acl_test *test, struct acl_pattern *pattern)
{
struct in_addr *s;
@@ -224,13 +211,12 @@
}
/* 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 @@
}
/* 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 @@
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 @@
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 @@
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 @@
* 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 *expr;
struct acl_keyword *aclkw;
struct acl_pattern *pattern;
+ int opaque;
const char *arg;
aclkw = find_acl_kw(args[0]);
@@ -423,14 +454,17 @@
/* 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;
diff --git a/src/client.c b/src/client.c
index 7c77189..ddc630b 100644
--- a/src/client.c
+++ b/src/client.c
@@ -517,14 +517,14 @@
/* 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", 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 },
+ { "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_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 },
}};
diff --git a/src/proto_http.c b/src/proto_http.c
index d636781..839bc94 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -5147,16 +5147,16 @@
* 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 @@
/* 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,15 +5286,15 @@
{ "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 },
- { "url_end", acl_parse_str, acl_fetch_url, acl_match_end },
- { "url_sub", acl_parse_str, acl_fetch_url, acl_match_sub },
- { "url_dir", acl_parse_str, acl_fetch_url, acl_match_dir },
- { "url_dom", acl_parse_str, acl_fetch_url, acl_match_dom },
- { "url_reg", acl_parse_reg, acl_fetch_url, acl_match_reg },
+ { "url", acl_parse_str, acl_fetch_url, acl_match_str },
+ { "url_beg", acl_parse_str, acl_fetch_url, acl_match_beg },
+ { "url_end", acl_parse_str, acl_fetch_url, acl_match_end },
+ { "url_sub", acl_parse_str, acl_fetch_url, acl_match_sub },
+ { "url_dir", acl_parse_str, acl_fetch_url, acl_match_dir },
+ { "url_dom", acl_parse_str, acl_fetch_url, acl_match_dom },
+ { "url_reg", acl_parse_reg, acl_fetch_url, acl_match_reg },
{ NULL, NULL, NULL, NULL },