]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] smarter integer comparison support in ACLs
authorWilly Tarreau <w@1wt.eu>
Sat, 9 Jun 2007 21:10:04 +0000 (23:10 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 9 Jun 2007 21:10:04 +0000 (23:10 +0200)
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.

doc/haproxy-en.txt
doc/haproxy-fr.txt
include/proto/acl.h
include/types/acl.h
src/acl.c
src/client.c
src/proto_http.c

index 73372e367e2e0b13681d460efcd63e88fa13fcf3..2a278b0a1dd009a8ff7636bb6af48750d173cb1d 100644 (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.
index ec746114962f94ed673d1c98bcfac0a190be6cd3..7d6ebf143a8a091be4975ee908b08bb51f8efefd 100644 (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.
index ace72dd9095380096ec298c4158101ad0cc584f5..59c213926396b1bc873fef913ae9bb20e9c5993a 100644 (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);
index 31f30fa13b22cff4d942a9e7685f88442ecbb3cf..02c1258b00670ba2d52d8bd5701a1d8b2cbefff1 100644 (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;
index 1dce3ea3f7e64df30a7dd671fef59382193aa1fe..efa785319091790dfad8071ca67ef66d4120ebfe 100644 (file)
--- a/src/acl.c
+++ b/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;
index 7c77189defaa89267bdc03ce734457e01477ded6..ddc630bf8e0e073251d1d2bb17eda71643f6f928 100644 (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",        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 },
 }};
 
index d6367819afde02d67e56a233652435fbe02e6a5e..839bc94d556faef5638d59111011c8d6909144be 100644 (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,15 +5286,15 @@ 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 },
-
-       { "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   },
+       { "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  },
 
        { NULL, NULL, NULL, NULL },