]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: proxy/checks: Register a keyword to parse tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 25 Mar 2020 17:20:15 +0000 (18:20 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:37 +0000 (09:39 +0200)
The keyword 'tcp-check' is now parsed in a dedicated callback function. Thus the
code to parse these rules is now located in checks.c. In addition, a deinit
function have been added to release proxy tcp-check rules, on error or when
HAProxy is stopped.

This patch is based on Gaetan Rivet work. It uses a callback function registerd
on the 'tcp-check' keyword instead, but the spirit is the same.

src/cfgparse-listen.c
src/checks.c

index e922c09baf519ef98abb30cbaa048432ad3afa5a..00b14239919149fcbb197908ca2bc43702e4fc75 100644 (file)
@@ -162,55 +162,6 @@ int warnif_misplaced_tcp_conn(struct proxy *proxy, const char *file, int line, c
                warnif_misplaced_tcp_sess(proxy, file, line, arg);
 }
 
-/* Parse a comment string for an expect check rule to find a potential
- * regex backreference. If so, check that it is valid.
- * returns:
- *   0 if none found.
- *   1 if at least one found and all are valid.
- *  -1 if at least one found and at least one is invalid.
- */
-static int find_and_check_backreferences(const char *str, char **err)
-{
-       static char *errors[] = {
-               "invalid backreference value",
-               "backreference is not within range [1, 9]",
-       };
-       char *backslash;
-       unsigned long int ref;
-       int found = 0;
-
-       while ((backslash = strchr(str, '\\'))) {
-               char *next, *end;
-
-               next = backslash + 1;
-               if (!isdigit(*next)) {
-                       str = next;
-                       continue;
-               }
-
-               errno = 0;
-               ref = strtoul(next, &end, 10);
-               if (errno == EINVAL) {
-                       *err = errors[0];
-                       return -1;
-               }
-               else if (errno == ERANGE) {
-                       *err = errors[1];
-                       return -1;
-               }
-
-               if (ref == 0 || ref > 9) {
-                       *err = errors[1];
-                       return -1;
-               }
-
-               found = 1;
-               str = end;
-       }
-
-       return found;
-}
-
 int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 {
        static struct proxy *curproxy = NULL;
@@ -3084,358 +3035,6 @@ stats_error_parsing:
                        goto out;
                }
        }
-       else if (!strcmp(args[0], "tcp-check")) {
-               if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
-                       err_code |= ERR_WARN;
-
-               if (curproxy == &defproxy) {
-                       ha_alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n",
-                                file, linenum, args[0]);
-                       err_code |= ERR_ALERT | ERR_FATAL;
-                       goto out;
-               }
-
-               if (curproxy->tcpcheck_rules == NULL) {
-                       curproxy->tcpcheck_rules = calloc(1, sizeof(*curproxy->tcpcheck_rules));
-                       if (curproxy->tcpcheck_rules == NULL) {
-                               ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
-                               err_code |= ERR_ALERT | ERR_ABORT;
-                               goto out;
-                       }
-                       LIST_INIT(curproxy->tcpcheck_rules);
-               }
-
-               if (strcmp(args[1], "comment") == 0) {
-                       int cur_arg;
-                       struct tcpcheck_rule *tcpcheck;
-
-                       cur_arg = 1;
-                       tcpcheck = calloc(1, sizeof(*tcpcheck));
-                       tcpcheck->action = TCPCHK_ACT_COMMENT;
-
-                       if (!*args[cur_arg + 1]) {
-                               ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
-                                        file, linenum, args[cur_arg]);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       }
-
-                       tcpcheck->comment = strdup(args[cur_arg + 1]);
-
-                       LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
-                       if (alertif_too_many_args_idx(1, 1, file, linenum, args, &err_code))
-                               goto out;
-               }
-               else if (strcmp(args[1], "connect") == 0) {
-                       const char *ptr_arg;
-                       int cur_arg;
-                       struct tcpcheck_rule *tcpcheck;
-
-                       /* check if first rule is also a 'connect' action */
-                       tcpcheck = LIST_NEXT(curproxy->tcpcheck_rules, struct tcpcheck_rule *, list);
-                       while (&tcpcheck->list != curproxy->tcpcheck_rules &&
-                              tcpcheck->action == TCPCHK_ACT_COMMENT) {
-                               tcpcheck = LIST_NEXT(&tcpcheck->list, struct tcpcheck_rule *, list);
-                       }
-
-                       if (&tcpcheck->list != curproxy->tcpcheck_rules
-                           && tcpcheck->action != TCPCHK_ACT_CONNECT) {
-                               ha_alert("parsing [%s:%d] : first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset.\n",
-                                        file, linenum);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       }
-
-                       cur_arg = 2;
-                       tcpcheck = calloc(1, sizeof(*tcpcheck));
-                       tcpcheck->action = TCPCHK_ACT_CONNECT;
-
-                       /* parsing each parameters to fill up the rule */
-                       while (*(ptr_arg = args[cur_arg])) {
-                               /* tcp port */
-                               if (strcmp(args[cur_arg], "port") == 0) {
-                                       if ( (atol(args[cur_arg + 1]) > 65535) ||
-                                                       (atol(args[cur_arg + 1]) < 1) ){
-                                               ha_alert("parsing [%s:%d] : '%s %s %s' expects a valid TCP port (from range 1 to 65535), got %s.\n",
-                                                        file, linenum, args[0], args[1], "port", args[cur_arg + 1]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       tcpcheck->port = atol(args[cur_arg + 1]);
-                                       cur_arg += 2;
-                               }
-                               /* send proxy protocol */
-                               else if (strcmp(args[cur_arg], "send-proxy") == 0) {
-                                       tcpcheck->conn_opts |= TCPCHK_OPT_SEND_PROXY;
-                                       cur_arg++;
-                               }
-#ifdef USE_OPENSSL
-                               else if (strcmp(args[cur_arg], "ssl") == 0) {
-                                       curproxy->options |= PR_O_TCPCHK_SSL;
-                                       tcpcheck->conn_opts |= TCPCHK_OPT_SSL;
-                                       cur_arg++;
-                               }
-#endif /* USE_OPENSSL */
-                               else if (strcmp(args[cur_arg], "linger") == 0) {
-                                       tcpcheck->conn_opts |= TCPCHK_OPT_LINGER;
-                                       cur_arg++;
-                               }
-                               /* comment for this tcpcheck line */
-                               else if (strcmp(args[cur_arg], "comment") == 0) {
-                                       if (!*args[cur_arg + 1]) {
-                                               ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
-                                                        file, linenum, args[cur_arg]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       tcpcheck->comment = strdup(args[cur_arg + 1]);
-                                       cur_arg += 2;
-                               }
-                               else {
-#ifdef USE_OPENSSL
-                                       ha_alert("parsing [%s:%d] : '%s %s' expects 'comment', 'port', 'send-proxy', 'ssl' or 'linger' but got '%s' as argument.\n",
-#else /* USE_OPENSSL */
-                                       ha_alert("parsing [%s:%d] : '%s %s' expects 'comment', 'port', 'send-proxy' or 'linger' but got '%s' as argument.\n",
-#endif /* USE_OPENSSL */
-                                                file, linenum, args[0], args[1], args[cur_arg]);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                       }
-
-                       LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
-               }
-               else if (strcmp(args[1], "send") == 0) {
-                       if (! *(args[2]) ) {
-                               /* SEND string expected */
-                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <STRING> as argument.\n",
-                                        file, linenum, args[0], args[1], args[2]);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       } else {
-                               struct tcpcheck_rule *tcpcheck;
-
-                               tcpcheck = calloc(1, sizeof(*tcpcheck));
-
-                               tcpcheck->action = TCPCHK_ACT_SEND;
-                               tcpcheck->string_len = strlen(args[2]);
-                               tcpcheck->string = strdup(args[2]);
-
-                               /* comment for this tcpcheck line */
-                               if (strcmp(args[3], "comment") == 0) {
-                                       if (!*args[4]) {
-                                               ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
-                                                        file, linenum, args[3]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       tcpcheck->comment = strdup(args[4]);
-                               }
-
-                               LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
-                       }
-               }
-               else if (strcmp(args[1], "send-binary") == 0) {
-                       if (! *(args[2]) ) {
-                               /* SEND binary string expected */
-                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument.\n",
-                                        file, linenum, args[0], args[1], args[2]);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       } else {
-                               struct tcpcheck_rule *tcpcheck;
-                               char *err = NULL;
-
-                               tcpcheck = calloc(1, sizeof(*tcpcheck));
-
-                               tcpcheck->action = TCPCHK_ACT_SEND;
-                               if (parse_binary(args[2], &tcpcheck->string, &tcpcheck->string_len, &err) == 0) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument, but %s\n",
-                                                file, linenum, args[0], args[1], args[2], err);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               /* comment for this tcpcheck line */
-                               if (strcmp(args[3], "comment") == 0) {
-                                       if (!*args[4]) {
-                                               ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
-                                                        file, linenum, args[3]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       tcpcheck->comment = strdup(args[4]);
-                               }
-
-                               LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
-                       }
-               }
-               else if (strcmp(args[1], "expect") == 0) {
-                       struct tcpcheck_rule *tcpcheck, *prev_check;
-                       struct tcpcheck_expect *expect;
-                       long min_recv = -1;
-                       const char *ptr_arg;
-                       int cur_arg;
-                       int inverse = 0;
-
-                       if (curproxy->options2 & PR_O2_EXP_TYPE) {
-                               ha_alert("parsing [%s:%d] : '%s %s' already specified.\n", file, linenum, args[0], args[1]);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       }
-
-                       cur_arg = 2;
-
-                       /* Parse potential the minimum amount of data
-                        * required before proceeding with the match.
-                        */
-                       if (strcmp(args[cur_arg], "min-recv") == 0) {
-                               if (!*(args[cur_arg + 1])) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects an integer as an argument.\n",
-                                                file, linenum, args[0], args[1], args[2]);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               /* Use an signed integer here because of chksize */
-                               min_recv = atol(args[cur_arg + 1]);
-                               if (min_recv < -1 || min_recv > INT_MAX) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects -1 or an integer from 0 to INT_MAX.\n",
-                                                file, linenum, args[0], args[1], args[2]);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               cur_arg += 2;
-                       }
-
-                       /* consider exclamation marks, sole or at the beginning of a word */
-                       while (*(ptr_arg = args[cur_arg])) {
-                               while (*ptr_arg == '!') {
-                                       inverse = !inverse;
-                                       ptr_arg++;
-                               }
-                               if (*ptr_arg)
-                                       break;
-                               cur_arg++;
-                       }
-
-                       /* now ptr_arg points to the beginning of a word past any possible
-                        * exclamation mark, and cur_arg is the argument which holds this word.
-                        */
-
-                       tcpcheck = calloc(1, sizeof(*tcpcheck));
-                       tcpcheck->action = TCPCHK_ACT_EXPECT;
-                       expect = &tcpcheck->expect;
-                       expect->inverse = inverse;
-                       expect->min_recv = min_recv;
-
-                       if (strcmp(ptr_arg, "binary") == 0) {
-                               char *err = NULL;
-
-                               if (!*(args[cur_arg + 1])) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <binary string> as an argument.\n",
-                                                file, linenum, args[0], args[1], ptr_arg);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               expect->type = TCPCHK_EXPECT_BINARY;
-                               if (parse_binary(args[cur_arg + 1], &expect->string, &expect->length, &err) == 0) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument, but %s\n",
-                                                file, linenum, args[0], args[1], args[2], err);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-                       }
-                       else if (strcmp(ptr_arg, "string") == 0) {
-                               if (!*(args[cur_arg + 1])) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <string> as an argument.\n",
-                                                file, linenum, args[0], args[1], ptr_arg);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               expect->type = TCPCHK_EXPECT_STRING;
-                               expect->string = strdup(args[cur_arg + 1]);
-                               expect->length = strlen(expect->string);
-                       }
-                       else if (strcmp(ptr_arg, "rstring") == 0 ||
-                                strcmp(ptr_arg, "rbinary") == 0) {
-                               if (!*(args[cur_arg + 1])) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
-                                                file, linenum, args[0], args[1], ptr_arg);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-
-                               expect->type = ((strcmp(ptr_arg, "rbinary") == 0) ? TCPCHK_EXPECT_REGEX_BINARY : TCPCHK_EXPECT_REGEX);
-                               error = NULL;
-                               if (!(expect->regex = regex_comp(args[cur_arg + 1], 1, 1, &error))) {
-                                       ha_alert("parsing [%s:%d] : '%s %s %s' : regular expression '%s': %s.\n",
-                                                file, linenum, args[0], args[1], ptr_arg, args[cur_arg + 1], error);
-                                       free(error);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-                       }
-                       else {
-                               ha_alert("parsing [%s:%d] : '%s %s' only supports [!] 'binary', 'string', 'rstring', 'rbinary', found '%s'.\n",
-                                        file, linenum, args[0], args[1], ptr_arg);
-                               err_code |= ERR_ALERT | ERR_FATAL;
-                               goto out;
-                       }
-
-                       /* tcpcheck comment */
-                       cur_arg += 2;
-                       if (strcmp(args[cur_arg], "comment") == 0) {
-                               if (!*args[cur_arg + 1]) {
-                                       ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
-                                                file, linenum, args[cur_arg + 1]);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-                               tcpcheck->comment = strdup(args[cur_arg + 1]);
-                               rc = find_and_check_backreferences(tcpcheck->comment, &error);
-                               if (rc > 0) {
-                                       if (!inverse) {
-                                               ha_warning("parsing [%s:%d] : "
-                                                          "using backreference in a positive expect comment is useless.\n",
-                                                          file, linenum);
-                                               err_code |= ERR_WARN;
-                                       }
-                                       expect->with_capture = 1;
-                               }
-                               else if (rc < 0) {
-                                       ha_alert("parsing [%s:%d] : %s.\n",
-                                                file, linenum, error);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-                       }
-
-                       /* All tcp-check expect points back to the first inverse expect rule
-                        * in a chain of one or more expect rule, potentially itself.
-                        */
-                       tcpcheck->expect.head = tcpcheck;
-                       list_for_each_entry_rev(prev_check, curproxy->tcpcheck_rules, list) {
-                               if (prev_check->action == TCPCHK_ACT_EXPECT) {
-                                       if (prev_check->expect.inverse)
-                                               tcpcheck->expect.head = prev_check;
-                                       continue;
-                               }
-                               if (prev_check->action != TCPCHK_ACT_COMMENT)
-                                       break;
-                       }
-                       LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
-               }
-               else {
-                       ha_alert("parsing [%s:%d] : '%s' only supports 'comment', 'connect', 'send' or 'expect'.\n", file, linenum, args[0]);
-                       err_code |= ERR_ALERT | ERR_FATAL;
-                       goto out;
-               }
-       }
        else if (!strcmp(args[0], "monitor")) {
                if (curproxy == &defproxy) {
                        ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
index 35d8f655f425028e7ab1742e614adad87664fa64..77008713cf16ff184b185a2cdb939a54b6b1fd2c 100644 (file)
@@ -30,6 +30,7 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include <common/cfgparse.h>
 #include <common/chunk.h>
 #include <common/compat.h>
 #include <common/config.h>
@@ -3366,6 +3367,31 @@ void free_check(struct check *check)
        }
 }
 
+static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
+{
+       if (!rule)
+               return;
+
+       free(rule->comment);
+       free(rule->string);
+       switch (rule->expect.type) {
+       case TCPCHK_EXPECT_STRING:
+       case TCPCHK_EXPECT_BINARY:
+               free(rule->expect.string);
+               break;
+       case TCPCHK_EXPECT_REGEX:
+       case TCPCHK_EXPECT_REGEX_BINARY:
+               regex_free(rule->expect.regex);
+               break;
+       case TCPCHK_EXPECT_UNDEF:
+               break;
+       }
+       if (in_pool)
+               pool_free(pool_head_tcpcheck_rule, rule);
+       else
+               free(rule);
+}
+
 void email_alert_free(struct email_alert *alert)
 {
        struct tcpcheck_rule *rule, *back;
@@ -3375,20 +3401,7 @@ void email_alert_free(struct email_alert *alert)
 
        list_for_each_entry_safe(rule, back, &alert->tcpcheck_rules, list) {
                LIST_DEL(&rule->list);
-               free(rule->comment);
-               switch (rule->expect.type) {
-               case TCPCHK_EXPECT_STRING:
-               case TCPCHK_EXPECT_BINARY:
-                       free(rule->expect.string);
-                       break;
-               case TCPCHK_EXPECT_REGEX:
-               case TCPCHK_EXPECT_REGEX_BINARY:
-                       regex_free(rule->expect.regex);
-                       break;
-               case TCPCHK_EXPECT_UNDEF:
-                       break;
-               }
-               pool_free(pool_head_tcpcheck_rule, rule);
+               free_tcpcheck(rule, 1);
        }
        pool_free(pool_head_email_alert, alert);
 }
@@ -3863,6 +3876,21 @@ static int init_srv_agent_check(struct server *srv)
        return ret;
 }
 
+static void deinit_proxy_tcpcheck(struct proxy *px)
+{
+       struct tcpcheck_rule *chk, *back;
+
+       if (!px->tcpcheck_rules)
+               return;
+
+       list_for_each_entry_safe(chk, back, px->tcpcheck_rules, list) {
+               LIST_DEL(&chk->list);
+               free_tcpcheck(chk, 0);
+       }
+       free(px->tcpcheck_rules);
+       px->tcpcheck_rules = NULL;
+}
+
 static void deinit_srv_check(struct server *srv)
 {
        if (srv->do_check)
@@ -3880,9 +3908,413 @@ static void deinit_srv_agent_check(struct server *srv)
 REGISTER_POST_SERVER_CHECK(init_srv_check);
 REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
 
+REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
 REGISTER_SERVER_DEINIT(deinit_srv_check);
 REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
 
+static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
+                                                   char **errmsg)
+{
+       struct tcpcheck_rule *chk = NULL;
+       char *comment = NULL;
+       unsigned short conn_opts = 0;
+       long port = 0;
+
+       list_for_each_entry(chk, rules, list) {
+               if (chk->action != TCPCHK_ACT_COMMENT)
+                       break;
+       }
+       if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
+               memprintf(errmsg, "first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset");
+               goto error;
+       }
+
+       cur_arg++;
+       while (*(args[cur_arg])) {
+               if (strcmp(args[cur_arg], "port") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a port number as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       port = atol(args[cur_arg]);
+                       if (port > 65535 || port < 1) {
+                               memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535), got %s.", args[cur_arg]);
+                               goto error;
+                       }
+               }
+               else if (strcmp(args[cur_arg], "comment") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       free(comment);
+                       comment = strdup(args[cur_arg]);
+                       if (!comment) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+               }
+               else if (strcmp(args[cur_arg], "send-proxy") == 0)
+                       conn_opts |= TCPCHK_OPT_SEND_PROXY;
+               else if (strcmp(args[cur_arg], "linger") == 0)
+                       conn_opts |= TCPCHK_OPT_LINGER;
+#ifdef USE_OPENSSL
+               else if (strcmp(args[cur_arg], "ssl") == 0) {
+                       px->options |= PR_O_TCPCHK_SSL;
+                       conn_opts |= TCPCHK_OPT_SSL;
+               }
+#endif /* USE_OPENSSL */
+
+               else {
+                       memprintf(errmsg, "expects 'comment', 'port', 'send-proxy'"
+#ifdef USE_OPENSSL
+                                 ", 'ssl'"
+#endif /* USE_OPENSSL */
+                                 " or 'linger' but got '%s' as argument.",
+                                 args[cur_arg]);
+                       goto error;
+               }
+               cur_arg++;
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action    = TCPCHK_ACT_CONNECT;
+       chk->port      = port;
+       chk->conn_opts = conn_opts;
+       chk->comment   = comment;
+       return chk;
+
+  error:
+       free(comment);
+       return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+       struct tcpcheck_rule *chk = NULL;
+       char *str = NULL, *comment = NULL;
+       int len, is_binary;
+
+       is_binary = (strcmp(args[cur_arg], "send-binary") == 0);
+       if (!*(args[cur_arg+1])) {
+               memprintf(errmsg, "'%s' expects a %s as argument", (is_binary ? "binary string": "string"), args[cur_arg]);
+               goto error;
+       }
+
+       if (is_binary) {
+               if (parse_binary(args[cur_arg+1], &str, &len, errmsg) == 0) {
+                       memprintf(errmsg, "'%s' invalid binary string (%s).\n", args[cur_arg], *errmsg);
+                       goto error;
+               }
+       }
+       else {
+               str = strdup(args[cur_arg+1]);
+               len = strlen(args[cur_arg+1]);
+               if (!str) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       cur_arg++;
+
+       if (strcmp(args[cur_arg], "comment") == 0) {
+               if (!*(args[cur_arg+1])) {
+                       memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                       goto error;
+               }
+               cur_arg++;
+               comment = strdup(args[cur_arg]);
+               if (!comment) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action     = TCPCHK_ACT_SEND;
+       chk->string     = str;
+       chk->string_len = len;
+       chk->comment    = comment;
+       return chk;
+
+  error:
+       free(str);
+       free(comment);
+       return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+       struct tcpcheck_rule *chk = NULL;
+       char *comment = NULL;
+
+       if (!*(args[cur_arg+1])) {
+               memprintf(errmsg, "expects a string as argument");
+               goto error;
+       }
+       cur_arg++;
+       comment = strdup(args[cur_arg]);
+       if (!comment) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action  = TCPCHK_ACT_COMMENT;
+       chk->comment = comment;
+       return chk;
+
+  error:
+       free(comment);
+       return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+       struct tcpcheck_rule *prev_check, *chk = NULL;
+       char *str = NULL, *comment = NULL, *pattern = NULL;
+       enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
+       long min_recv = -1;
+       int inverse = 0, with_capture = 0;
+
+       if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
+               memprintf(errmsg, "expects a pattern (type+string) as arguments");
+               goto error;
+       }
+
+       cur_arg++;
+       while (*(args[cur_arg])) {
+               int in_pattern = 0;
+
+         rescan:
+               if (strcmp(args[cur_arg], "min-recv") == 0) {
+                       if (in_pattern) {
+                               memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+                               goto error;
+                       }
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       /* Use an signed integer here because of chksize */
+                       cur_arg++;
+                       min_recv = atol(args[cur_arg]);
+                       if (min_recv < -1 || min_recv > INT_MAX) {
+                               memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
+                               goto error;
+                       }
+               }
+               else if (*(args[cur_arg]) == '!') {
+                       in_pattern = 1;
+                       while (*(args[cur_arg]) == '!') {
+                               inverse = !inverse;
+                               args[cur_arg]++;
+                       }
+                       if (!*(args[cur_arg]))
+                               cur_arg++;
+                       goto rescan;
+               }
+               else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
+                        strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
+                       if (type != TCPCHK_EXPECT_UNDEF) {
+                               memprintf(errmsg, "only on pattern expected");
+                               goto error;
+                       }
+                       type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
+                               ((*(args[cur_arg]) == 'b') ?  TCPCHK_EXPECT_BINARY :
+                                ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
+
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       pattern = args[cur_arg];
+               }
+               else if (strcmp(args[cur_arg], "comment") == 0) {
+                       if (in_pattern) {
+                               memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+                               goto error;
+                       }
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       free(comment);
+                       comment = strdup(args[cur_arg]);
+                       if (!comment) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+               }
+               else {
+                       memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
+                                 " or comment but got '%s' as argument.", args[cur_arg]);
+                       goto error;
+               }
+
+               cur_arg++;
+       }
+
+       if (comment) {
+               char *p = comment;
+
+               while (*p) {
+                       if (*p == '\\') {
+                               p++;
+                               if (!*p || !isdigit((unsigned char)*p) ||
+                                   (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
+                                       memprintf(errmsg, "invalid backreference in 'comment' argument");
+                                       goto error;
+                               }
+                               with_capture = 1;
+                       }
+                       p++;
+               }
+               if (with_capture && !inverse)
+                       memprintf(errmsg, "using backreference in a positive expect comment is useless");
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action  = TCPCHK_ACT_EXPECT;
+       chk->comment = comment;
+       chk->expect.type = type;
+       chk->expect.min_recv = min_recv;
+       chk->expect.inverse = inverse;
+       chk->expect.with_capture = with_capture;
+
+       switch (chk->expect.type) {
+       case TCPCHK_EXPECT_STRING:
+               chk->expect.string = strdup(pattern);
+               chk->expect.length = strlen(pattern);
+               if (!chk->expect.string) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+               break;
+       case TCPCHK_EXPECT_BINARY:
+               if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
+                       memprintf(errmsg, "invalid binary string (%s)", *errmsg);
+                       goto error;
+               }
+       case TCPCHK_EXPECT_REGEX:
+       case TCPCHK_EXPECT_REGEX_BINARY:
+               chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
+               if (!chk->expect.regex)
+                       goto error;
+               break;
+       case TCPCHK_EXPECT_UNDEF:
+               free(chk);
+               memprintf(errmsg, "pattern not found");
+               goto error;
+       }
+
+       /* All tcp-check expect points back to the first inverse expect rule in
+        * a chain of one or more expect rule, potentially itself.
+        */
+       chk->expect.head = chk;
+       list_for_each_entry_rev(prev_check, rules, list) {
+               if (prev_check->action == TCPCHK_ACT_EXPECT) {
+                       if (prev_check->expect.inverse)
+                               chk->expect.head = prev_check;
+                       continue;
+               }
+               if (prev_check->action != TCPCHK_ACT_COMMENT)
+                       break;
+       }
+       return chk;
+
+  error:
+       free(chk);
+       free(str);
+       free(comment);
+       return NULL;
+}
+
+/* Parses the "tcp-check" proxy keyword */
+static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
+                               struct proxy *defpx, const char *file, int line,
+                               char **errmsg)
+{
+       struct list *rules = curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk = NULL;
+       int cur_arg, ret = 0;
+
+       if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
+               ret = 1;
+
+       if (curpx == defpx) {
+               memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
+               goto error;
+       }
+
+       if (!rules) {
+               rules = calloc(1, sizeof(*rules));
+               if (!rules) {
+                       memprintf(errmsg, "%s : out of memory.", args[0]);
+                       goto error;
+               }
+               LIST_INIT(rules);
+               curpx->tcpcheck_rules = rules;
+       }
+
+       cur_arg = 1;
+       if (strcmp(args[cur_arg], "connect") == 0)
+               chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules, errmsg);
+       else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
+               chk = parse_tcpcheck_send(args, cur_arg, rules, errmsg);
+       else if (strcmp(args[cur_arg], "expect") == 0)
+               chk = parse_tcpcheck_expect(args, cur_arg, rules, errmsg);
+       else if (strcmp(args[cur_arg], "comment") == 0)
+               chk = parse_tcpcheck_comment(args, cur_arg, rules, errmsg);
+       else {
+               memprintf(errmsg, "'%s %s' only supports 'comment', 'connect', 'send', 'send-binary' or 'expect'.",
+                         args[0], args[1]);
+               goto error;
+       }
+
+       if (!chk) {
+               memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
+               goto error;
+       }
+       ret = (*errmsg != NULL); /* Handle warning */
+
+       /* No error: add the tcp-check rule in the list */
+       LIST_ADDQ(rules, &chk->list);
+       return ret;
+
+  error:
+       if (rules)
+               deinit_proxy_tcpcheck(curpx);
+       return -1;
+}
+
+static struct cfg_kw_list cfg_kws = {ILH, {
+        { CFG_LISTEN, "tcp-check",  proxy_parse_tcpcheck },
+        { 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
+
 /*
  * Local variables:
  *  c-indent-level: 8