]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: add send/expect tcp based check
authorBaptiste Assmann <bedis9@gmail.com>
Sun, 6 Oct 2013 21:24:13 +0000 (23:24 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 6 Dec 2013 10:50:47 +0000 (11:50 +0100)
This is a generic health check which can be used to match a
banner or send a request and analyse a server response.
It works in a send/expect ways and many exchange can be done between
HAProxy and a server to decide the server status, making HAProxy able to
speak the server's protocol.

It can send arbitrary regular or binary strings and match content as a
regular or binary string or a regex.

Signed-off-by: Baptiste Assmann <bedis9@gmail.com>
doc/configuration.txt
include/types/checks.h
include/types/proxy.h
include/types/server.h
src/cfgparse.c
src/checks.c
src/proxy.c

index 81b42954ef82ce56eb64896779ad3658ed633627..1e71342577bdca1f93d0413464d83ba3e58cd00b 100644 (file)
@@ -1144,6 +1144,9 @@ http-check expect                         -          -         X         X
 http-check send-state                     X          -         X         X
 http-request                              -          X         X         X
 http-response                             -          X         X         X
+tcp-check expect                          -          -         X         X
+tcp-check send                            -          -         X         X
+tcp-check send-binary                     -          -         X         X
 http-send-name-header                     -          -         X         X
 id                                        -          X         X         X
 ignore-persist                            -          X         X         X
@@ -1165,6 +1168,7 @@ option dontlognull                   (*)  X          X         X         -
 option forceclose                    (*)  X          X         X         X
 -- keyword -------------------------- defaults - frontend - listen -- backend -
 option forwardfor                         X          X         X         X
+option tcp-check                          X          -         X         X
 option http-no-delay                 (*)  X          X         X         X
 option http-pretend-keepalive        (*)  X          X         X         X
 option http-server-close             (*)  X          X         X         X
@@ -2936,6 +2940,132 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
   See also : "http-request", section 3.4 about userlists and section 7 about
              ACL usage.
 
+
+tcp-check expect [!] <match> <pattern>
+  Specify data to be collected and analysed during a generic health check
+  May be used in sections:   defaults | frontend | listen | backend
+                               no     |    no    |   yes  |   yes
+
+  Arguments :
+    <match>   is a keyword indicating how to look for a specific pattern in the
+              response. The keyword may be one of "string", "rstring" or
+              binary.
+              The keyword may be preceded by an exclamation mark ("!") to negate
+              the match. Spaces are allowed between the exclamation mark and the
+              keyword. See below for more details on the supported keywords.
+
+    <pattern> is the pattern to look for. It may be a string or a regular
+              expression. If the pattern contains spaces, they must be escaped
+              with the usual backslash ('\').
+              If the match is set to binary, then the pattern must be passed as
+              a serie of hexadecimal digits in an even number. Each sequence of
+              two digits will represent a byte. The hexadecimal digits may be
+              used upper or lower case.
+
+
+  The available matches are intentionally similar to their http-check cousins :
+
+    string <string> : test the exact string matches in the response buffer.
+                      A health check response will be considered valid if the
+                      response's buffer contains this exact string. If the
+                      "string" keyword is prefixed with "!", then the response
+                      will be considered invalid if the body contains this
+                      string. This can be used to look for a mandatory pattern
+                      in a protocol response, or to detect a failure when a
+                      specific error appears in a protocol banner.
+
+    rstring <regex> : test a regular expression on the response buffer.
+                      A health check response will be considered valid if the
+                      response's buffer matches this expression. If the
+                      "rstring" keyword is prefixed with "!", then the response
+                      will be considered invalid if the body matches the
+                      expression.
+
+    binary <hexstring> : test the exact string in its hexadecimal form matches
+                         in the response buffer. A health check response will
+                         be considered valid if the response's buffer contains
+                         this exact hexadecimal string.
+                         Purpose is to match data on binary protocols.
+
+  It is important to note that the responses will be limited to a certain size
+  defined by the global "tune.chksize" option, which defaults to 16384 bytes.
+  Thus, too large responses may not contain the mandatory pattern when using
+  "string", "rstring" or binary. If a large response is absolutely required, it
+  is possible to change the default max size by setting the global variable.
+  However, it is worth keeping in mind that parsing very large responses can
+  waste some CPU cycles, especially when regular expressions are used, and that
+  it is always better to focus the checks on smaller resources. Also, in its
+  current state, the check will not find any string nor regex past a null
+  character in the response. Similarly it is not possible to request matching
+  the null character.
+
+  Examples :
+         # perform a POP check
+         option tcp-check
+         tcp-check expect string +OK\ POP3\ ready
+
+         # perform an IMAP check
+         option tcp-check
+         tcp-check expect string *\ OK\ IMAP4\ ready
+
+         # look for the redis master server
+         option tcp-check
+         tcp-check send PING\r\n
+         tcp-check expect +PONG
+         tcp-check send info\ replication\r\n
+         tcp-check expect string role:master
+         tcp-check send QUIT\r\n
+         tcp-check expect string +OK
+
+
+  See also : "option tcp-check", "tcp-check send", "http-check expect",
+             tune.chksize
+
+
+tcp-check send <data>
+  Specify a string to be sent as a question during a generic health check
+  May be used in sections:   defaults | frontend | listen | backend
+                               no     |    no    |   yes  |   yes
+
+    <data> : the data to be sent as a question during a generic health check
+             session. For now, <data> must be a string.
+
+  Examples :
+         # look for the redis master server
+         option tcp-check
+         tcp-check send info\ replication\r\n
+         tcp-check expect string role:master
+
+  See also : "option tcp-check", "tcp-check expect", "tcp-check send-binary",
+             tune.chksize
+
+
+tcp-check send-binary <hexastring>
+  Specify an hexa digits string to be sent as a binary question during a raw
+  tcp health check
+  May be used in sections:   defaults | frontend | listen | backend
+                               no     |    no    |   yes  |   yes
+
+    <data> : the data to be sent as a question during a generic health check
+             session. For now, <data> must be a string.
+    <hexastring> : test the exact string in its hexadecimal form matches in the
+                   response buffer. A health check response will be considered
+                   valid if the response's buffer contains this exact
+                   hexadecimal string.
+                   Purpose is to send binary data to ask on binary protocols.
+
+  Examples :
+         # redis check in binary
+         option tcp-check
+         tcp-check send-binary 50494e470d0a # PING\r\n
+         tcp-check expect binary 2b504F4e47 # +PONG
+
+
+  See also : "option tcp-check", "tcp-check expect", "tcp-check send",
+             tune.chksize
+
+
+
 http-send-name-header [<header>]
   Add the server name to a request. Use the header string given by <header>
 
@@ -3631,6 +3761,75 @@ option forwardfor [ except <network> ] [ header <name> ] [ if-none ]
              "option forceclose"
 
 
+option tcp-check
+  Perform health checks using tcp-check send/expect sequences
+  May be used in sections:   defaults | frontend | listen | backend
+                               yes    |    no    |   yes  |   yes
+
+  This health check method is intended to be combined with "tcp-check" command
+  lists in order to support send/expect types of health check sequences.
+
+  TCP checks currently support 4 modes of operations :
+    - no "tcp-check" directive : the health check only consists in a connection
+      attempt, which remains the default mode.
+
+    - "tcp-check send" or "tcp-check send-binary" only is mentionned : this is
+      used to send a string along with a connection opening. With some
+      protocols, it helps sending a "QUIT" message for example that prevents
+      the server from logging a connection error for each health check. The
+      check result will still be based on the ability to open the connection
+      only.
+
+    - "tcp-check expect" only is mentionned : this is used to test a banner.
+      The connection is opened and haproxy waits for the server to present some
+      contents which must validate some rules. The check result will be based
+      on the matching between the contents and the rules. This is suited for
+      POP, IMAP, SMTP, FTP, SSH, TELNET.
+
+    - both "tcp-check send" and "tcp-check expect" are mentionned : this is
+      used to test a hello-type protocol. Haproxy sends a message, the server
+      responds and its response is analysed. the check result will be based on
+      the maching between the response contents and the rules. This is often
+      suited for protocols which require a binding or a request/response model.
+      LDAP, MySQL, Redis and SSL are example of such protocols, though they
+      already all have their dedicated checks with a deeper understanding of
+      the respective protocols.
+      In this mode, many questions may be sent and many answers may be
+      analysed.
+
+  Examples :
+         # perform a POP check (analyse only server's banner)
+         option tcp-check
+         tcp-check expect string +OK\ POP3\ ready
+
+         # perform an IMAP check (analyse only server's banner)
+         option tcp-check
+         tcp-check expect string *\ OK\ IMAP4\ ready
+
+         # look for the redis master server after ensuring it speaks well
+         # redis protocol, then it exits properly.
+         # (send a command then analyse the response 3 tims)
+         option tcp-check
+         tcp-check send PING\r\n
+         tcp-check expect +PONG
+         tcp-check send info\ replication\r\n
+         tcp-check expect string role:master
+         tcp-check send QUIT\r\n
+         tcp-check expect string +OK
+
+         forge a HTTP request, then analyse the response
+         (send many headers before analyzing)
+         option tcp-check
+         tcp-check send HEAD\ /\ HTTP/1.1\r\n
+         tcp-check send Host:\ www.mydomain.com\r\n
+         tcp-check send User-Agent:\ HAProxy\ tcpcheck\r\n
+         tcp-check send \r\n
+         tcp-check expect rstring HTTP/1\..\ (2..|3..)
+
+
+  See also : "tcp-check expect", "tcp-check send"
+
+
 option http-no-delay
 no option http-no-delay
   Instruct the system to favor low interactive delays over performance in HTTP
index 09a4eee013b9234ec25f372c56cdbf9591d99ba2..d7d9725329bdf925ddde60e6b5afd299a79921a3 100644 (file)
@@ -106,4 +106,21 @@ struct analyze_status {
        unsigned char lr[HANA_OBS_SIZE];        /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
 };
 
+/* bits for tcpcheck_rule->action */
+enum {
+       TCPCHK_ACT_SEND        = 1,             /* send action, regular string format */
+       TCPCHK_ACT_EXPECT,                      /* expect action, either regular or binary string */
+};
+
+struct tcpcheck_rule {
+       struct list list;                       /* list linked to from the proxy */
+       int action;                             /* action: send or expect */
+       /* match type uses NON-NULL pointer from either string or expect_regex below */
+       /* sent string is string */
+       char *string;                           /* sent or expected string */
+       int string_len;                         /* string lenght */
+       regex_t *expect_regex;                  /* expected */
+       int inverse;                            /* 0 = regular match, 1 = inverse match */
+};
+
 #endif /* _TYPES_CHECKS_H */
index 8b9f2fbf5ff66b3562ce7244a5891cd8b46c525b..4883827613a315364ff170628aca050c17bb0fd5 100644 (file)
@@ -152,7 +152,8 @@ enum {
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
 #define PR_O2_SSL3_CHK  0x70000000      /* use SSLv3 CLIENT_HELLO packets for server health */
 #define PR_O2_LB_AGENT_CHK 0x80000000   /* use a TCP connection to obtain a metric of server health */
-/* unused: 0x90000000 to 0xF000000, reserved for health checks */
+#define PR_O2_TCPCHK_CHK 0x90000000     /* use TCPCHK check for server health */
+/* unused: 0xA0000000 to 0xF000000, reserved for health checks */
 #define PR_O2_CHK_ANY   0xF0000000      /* Mask to cover any check */
 /* end of proxy->options2 */
 
@@ -324,6 +325,7 @@ struct proxy {
 
        struct task *task;                      /* the associated task, mandatory to manage rate limiting, stopping and resource shortage, NULL if disabled */
        int grace;                              /* grace time after stop request */
+       struct list tcpcheck_rules;             /* tcp-check send / expect rules */
        char *check_req;                        /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */
        int check_len;                          /* Length of the HTTP or SSL3 request */
        char *expect_str;                       /* http-check expected content : string or text version of the regex */
index 6b73a3954234125a8c047499fe7af16a4cd5ff9e..82eb55fdb707c3115d6db9ee1dd1b22e2d0a5428 100644 (file)
@@ -122,6 +122,7 @@ struct check {
        char desc[HCHK_DESC_LEN];               /* health check descritpion */
        int use_ssl;                            /* use SSL for health checks */
        int send_proxy;                         /* send a PROXY protocol header with checks */
+       struct tcpcheck_rule *current_step;     /* current step when using tcpcheck */
        int inter, fastinter, downinter;        /* checks: time in milliseconds */
        int result;                             /* health-check result : SRV_CHK_* */
        int state;                              /* health-check result : CHK_* */
index e267de8c8330037d7b06d913a150ee3fe9367f46..73a08a42a185729610702a18c339ca3c276a5c56 100644 (file)
@@ -3732,6 +3732,16 @@ stats_error_parsing:
                        memcpy(curproxy->check_req, DEF_LDAP_CHECK_REQ, sizeof(DEF_LDAP_CHECK_REQ) - 1);
                        curproxy->check_len = sizeof(DEF_LDAP_CHECK_REQ) - 1;
                }
+               else if (!strcmp(args[1], "tcp-check")) {
+                       /* use raw TCPCHK send/expect to check servers' health */
+                       if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
+                               err_code |= ERR_WARN;
+
+                       free(curproxy->check_req);
+                       curproxy->check_req = NULL;
+                       curproxy->options2 &= ~PR_O2_CHK_ANY;
+                       curproxy->options2 |= PR_O2_TCPCHK_CHK;
+               }
                else if (!strcmp(args[1], "forwardfor")) {
                        int cur_arg;
 
@@ -3969,6 +3979,161 @@ 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 (strcmp(args[1], "send") == 0) {
+                       if (! *(args[2]) ) {
+                               /* SEND string expected */
+                               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 = (struct tcpcheck_rule *)calloc(1, sizeof(*tcpcheck));
+
+                               tcpcheck->action = TCPCHK_ACT_SEND;
+                               tcpcheck->string_len = strlen(args[2]);
+                               tcpcheck->string = strdup(args[2]);
+                               tcpcheck->expect_regex = NULL;
+
+                               LIST_ADDQ(&curproxy->tcpcheck_rules, &tcpcheck->list);
+                       }
+               }
+               else if (strcmp(args[1], "send-binary") == 0) {
+                       if (! *(args[2]) ) {
+                               /* SEND binary string expected */
+                               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 = (struct tcpcheck_rule *)calloc(1, sizeof(*tcpcheck));
+
+                               tcpcheck->action = TCPCHK_ACT_SEND;
+                               if (parse_binary(args[2], &tcpcheck->string, &tcpcheck->string_len, &err) == 0) {
+                                       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;
+                               }
+                               tcpcheck->expect_regex = NULL;
+
+                               LIST_ADDQ(&curproxy->tcpcheck_rules, &tcpcheck->list);
+                       }
+               }
+               else if (strcmp(args[1], "expect") == 0) {
+                       const char *ptr_arg;
+                       int cur_arg;
+                       int inverse = 0;
+
+                       if (curproxy->options2 & PR_O2_EXP_TYPE) {
+                               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;
+                       /* 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.
+                        */
+                       if (strcmp(ptr_arg, "binary") == 0) {
+                               if (!*(args[cur_arg + 1])) {
+                                       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;
+                               }
+                               struct tcpcheck_rule *tcpcheck;
+                               char *err = NULL;
+
+                               tcpcheck = (struct tcpcheck_rule *)calloc(1, sizeof(*tcpcheck));
+
+                               tcpcheck->action = TCPCHK_ACT_EXPECT;
+                               if (parse_binary(args[cur_arg + 1], &tcpcheck->string, &tcpcheck->string_len, &err) == 0) {
+                                       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;
+                               }
+                               tcpcheck->expect_regex = NULL;
+                               tcpcheck->inverse = inverse;
+
+                               LIST_ADDQ(&curproxy->tcpcheck_rules, &tcpcheck->list);
+                       }
+                       else if (strcmp(ptr_arg, "string") == 0) {
+                               if (!*(args[cur_arg + 1])) {
+                                       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;
+                               }
+                               struct tcpcheck_rule *tcpcheck;
+
+                               tcpcheck = (struct tcpcheck_rule *)calloc(1, sizeof(*tcpcheck));
+
+                               tcpcheck->action = TCPCHK_ACT_EXPECT;
+                               tcpcheck->string_len = strlen(args[cur_arg + 1]);
+                               tcpcheck->string = strdup(args[cur_arg + 1]);
+                               tcpcheck->expect_regex = NULL;
+                               tcpcheck->inverse = inverse;
+
+                               LIST_ADDQ(&curproxy->tcpcheck_rules, &tcpcheck->list);
+                       }
+                       else if (strcmp(ptr_arg, "rstring") == 0) {
+                               if (!*(args[cur_arg + 1])) {
+                                       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;
+                               }
+                               struct tcpcheck_rule *tcpcheck;
+
+                               tcpcheck = (struct tcpcheck_rule *)calloc(1, sizeof(*tcpcheck));
+
+                               tcpcheck->action = TCPCHK_ACT_EXPECT;
+                               tcpcheck->string_len = 0;
+                               tcpcheck->string = NULL;
+                               tcpcheck->expect_regex = calloc(1, sizeof(regex_t));
+                               if (regcomp(tcpcheck->expect_regex, args[cur_arg + 1], REG_EXTENDED) != 0) {
+                                       Alert("parsing [%s:%d] : '%s %s %s' : bad regular expression '%s'.\n",
+                                             file, linenum, args[0], args[1], ptr_arg, args[cur_arg + 1]);
+                                       err_code |= ERR_ALERT | ERR_FATAL;
+                                       goto out;
+                               }
+                               tcpcheck->inverse = inverse;
+
+                               LIST_ADDQ(&curproxy->tcpcheck_rules, &tcpcheck->list);
+                       }
+                       else {
+                               Alert("parsing [%s:%d] : '%s %s' only supports [!] 'binary', 'string', 'rstring', found '%s'.\n",
+                                     file, linenum, args[0], args[1], ptr_arg);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+               }
+               else {
+                       Alert("parsing [%s:%d] : '%s' only supports '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) {
                        Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
index 56f353767d97898e0f0490d0c700c720ccc4bb4c..10aa41ada98d54385e17d92d95c2c6563dd1465c 100644 (file)
@@ -52,6 +52,8 @@
 #include <proto/task.h>
 
 static int httpchk_expect(struct server *s, int done);
+static int tcpcheck_get_step_id(struct server *);
+static void tcpcheck_main(struct connection *);
 
 static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
        [HCHK_STATUS_UNKNOWN]   = { SRV_CHK_UNKNOWN,                   "UNK",     "Unknown" },
@@ -834,6 +836,7 @@ static void chk_report_conn_err(struct connection *conn, int errno_bck, int expi
 {
        struct check *check = conn->owner;
        const char *err_msg;
+       struct chunk *chk;
 
        if (check->result != SRV_CHK_UNKNOWN)
                return;
@@ -850,20 +853,36 @@ static void chk_report_conn_err(struct connection *conn, int errno_bck, int expi
         * socket error possibly collected above. This is useful to know the
         * exact step of the L6 layer (eg: SSL handshake).
         */
+       chk = get_trash_chunk();
+
+       if (check->type == PR_O2_TCPCHK_CHK) {
+               chunk_printf(chk, " at step %d of tcp-check", tcpcheck_get_step_id(check->server));
+               /* we were looking for a string */
+               if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
+                       if (check->current_step->string)
+                               chunk_appendf(chk, " (string '%s')", check->current_step->string);
+                       else if (check->current_step->expect_regex)
+                               chunk_appendf(chk, " (expect regex)");
+               }
+               else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
+                       chunk_appendf(chk, " (send)");
+               }
+       }
+
        if (conn->err_code) {
                if (errno && errno != EAGAIN)
-                       chunk_printf(&trash, "%s (%s)", conn_err_code_str(conn), strerror(errno));
+                       chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno), chk->str);
                else
-                       chunk_printf(&trash, "%s", conn_err_code_str(conn));
+                       chunk_printf(&trash, "%s%s", conn_err_code_str(conn), chk->str);
                err_msg = trash.str;
        }
        else {
                if (errno && errno != EAGAIN) {
-                       chunk_printf(&trash, "%s", strerror(errno));
+                       chunk_printf(&trash, "%s%s", strerror(errno), chk->str);
                        err_msg = trash.str;
                }
                else {
-                       err_msg = NULL;
+                       err_msg = chk->str;
                }
        }
 
@@ -933,6 +952,11 @@ static void event_srv_chk_w(struct connection *conn)
        if (!check->type)
                goto out_wakeup;
 
+       if (check->type == PR_O2_TCPCHK_CHK) {
+               tcpcheck_main(conn);
+               return;
+       }
+
        if (check->bo->o) {
                conn->xprt->snd_buf(conn, check->bo, MSG_DONTWAIT | MSG_NOSIGNAL);
                if (conn->flags & CO_FL_ERROR) {
@@ -986,6 +1010,11 @@ static void event_srv_chk_r(struct connection *conn)
        if (conn->flags & (CO_FL_HANDSHAKE | CO_FL_WAIT_RD))
                return;
 
+       if (check->type == PR_O2_TCPCHK_CHK) {
+               tcpcheck_main(conn);
+               return;
+       }
+
        /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available
         * but the connection was closed on the remote end. Fortunately, recv still
         * works correctly and we don't need to do the getsockopt() on linux.
@@ -1481,11 +1510,17 @@ static struct task *process_chk(struct task *t)
                check->bo->p = check->bo->data;
                check->bo->o = 0;
 
-              /* prepare the check buffer
-               * This should not be used if check is the secondary agent check
-               * of a server as s->proxy->check_req will relate to the
-               * configuration of the primary check */
-              if (check->type && check != &s->agent) {
+               /* tcpcheck send/expect initialisation */
+               if (check->type == PR_O2_TCPCHK_CHK)
+                       check->current_step = NULL;
+
+               /* prepare the check buffer.
+                * This should not be used if check is the secondary agent check
+                * of a server as s->proxy->check_req will relate to the
+                * configuration of the primary check. Similarly, tcp-check uses
+                * its own strings.
+                */
+               if (check->type && check->type != PR_O2_TCPCHK_CHK && check != &s->agent) {
                        bo_putblk(check->bo, s->proxy->check_req, s->proxy->check_len);
 
                        /* we want to check if this host replies to HTTP or SSLv3 requests
@@ -1863,6 +1898,278 @@ static int httpchk_expect(struct server *s, int done)
        return 1;
 }
 
+/*
+ * return the id of a step in a send/expect session
+ */
+static int tcpcheck_get_step_id(struct server *s)
+{
+       struct tcpcheck_rule *cur = NULL, *next = NULL;
+       int i = 0;
+
+       cur = s->check.current_step;
+
+       /* no step => first step */
+       if (cur == NULL)
+               return 1;
+
+       /* increment i until current step */
+       list_for_each_entry(next, &s->proxy->tcpcheck_rules, list) {
+               if (next->list.p == &cur->list)
+                       break;
+               ++i;
+       }
+
+       return i;
+}
+
+static void tcpcheck_main(struct connection *conn)
+{
+       char *contentptr;
+       unsigned int contentlen;
+       struct list *head = NULL;
+       struct tcpcheck_rule *cur = NULL;
+       int done = 0, ret = 0;
+
+       struct check *check = conn->owner;
+       struct server *s = check->server;
+       struct task *t = check->task;
+
+       /* don't do anything until the connection is established */
+       if (!(conn->flags & CO_FL_CONNECTED)) {
+               /* update expire time, should be done by process_chk */
+               /* we allow up to min(inter, timeout.connect) for a connection
+                * to establish but only when timeout.check is set
+                * as it may be to short for a full check otherwise
+                */
+               while (tick_is_expired(t->expire, now_ms)) {
+                       int t_con;
+
+                       t_con = tick_add(t->expire, s->proxy->timeout.connect);
+                       t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
+
+                       if (s->proxy->timeout.check)
+                               t->expire = tick_first(t->expire, t_con);
+               }
+               return;
+       }
+
+       /* here, we know that the connection is established */
+       if (check->result & (SRV_CHK_FAILED | SRV_CHK_PASSED)) {
+               goto out_end_tcpcheck;
+       }
+
+       /* head is be the first element of the double chained list */
+       head = &s->proxy->tcpcheck_rules;
+
+       /* no step means first step
+        * initialisation */
+       if (check->current_step == NULL) {
+               check->bo->p = check->bo->data;
+               check->bo->o = 0;
+               check->bi->p = check->bi->data;
+               check->bi->i = 0;
+               cur = check->current_step = LIST_ELEM(head->n, struct tcpcheck_rule *, list);
+               t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
+               if (s->proxy->timeout.check)
+                       t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
+       }
+       /* keep on processing step */
+       else {
+               cur = check->current_step;
+       }
+
+       while (&cur->list != head) {
+               if (check->current_step->action & TCPCHK_ACT_SEND) {
+                       /* reset the read buffer */
+                       if (*check->bi->data != '\0') {
+                               *check->bi->data = '\0';
+                               check->bi->i = 0;
+                       }
+
+                       if (conn->flags & (CO_FL_SOCK_WR_SH | CO_FL_DATA_WR_SH)) {
+                               conn->flags |= CO_FL_ERROR;
+                               chk_report_conn_err(conn, 0, 0);
+                               goto out_end_tcpcheck;
+                       }
+
+                       if (conn->flags & (CO_FL_HANDSHAKE | CO_FL_WAIT_WR))
+                               return;
+
+                       /* disable reading for now */
+                       //if (conn->flags & (CO_FL_WAIT_RD | CO_FL_DATA_RD_ENA))
+                       __conn_data_stop_recv(conn);
+
+                       bo_putblk(check->bo, check->current_step->string, check->current_step->string_len);
+                       *check->bo->p = '\0'; /* to make gdb output easier to read */
+
+                       if (check->bo->o) {
+                               conn->xprt->snd_buf(conn, check->bo, MSG_DONTWAIT | MSG_NOSIGNAL);
+                               if (conn->flags & CO_FL_ERROR) {
+                                       chk_report_conn_err(conn, errno, 0);
+                                       __conn_data_stop_both(conn);
+                                       goto out_end_tcpcheck;
+                               }
+                       }
+                       cur = (struct tcpcheck_rule *)cur->list.n;
+                       check->current_step = cur;
+
+                       if (check->bo->o)
+                               goto out_incomplete;
+
+                       __conn_data_stop_send(conn);   /* nothing more to write */
+               } /* end 'send' */
+               else if (check->current_step->action & TCPCHK_ACT_EXPECT) {
+                       if (unlikely(check->result & SRV_CHK_FAILED))
+                               goto out_end_tcpcheck;
+
+                       if (conn->flags & (CO_FL_HANDSHAKE | CO_FL_WAIT_RD))
+                               return;
+
+                       conn->xprt->rcv_buf(conn, check->bi, buffer_total_space(check->bi));
+
+                       if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH | CO_FL_DATA_RD_SH)) {
+                               done = 1;
+                               if ((conn->flags & CO_FL_ERROR) && !check->bi->i) {
+                                       /* Report network errors only if we got no other data. Otherwise
+                                        * we'll let the upper layers decide whether the response is OK
+                                        * or not. It is very common that an RST sent by the server is
+                                        * reported as an error just after the last data chunk.
+                                        */
+                                       chk_report_conn_err(conn, errno, 0);
+                                       goto out_end_tcpcheck;
+                               }
+                       }
+
+                       /* Intermediate or complete response received.
+                        * Terminate string in check->bi->data buffer.
+                        */
+                       if (check->bi->i < check->bi->size) {
+                               check->bi->data[check->bi->i] = '\0';
+                       }
+                       else {
+                               check->bi->data[check->bi->i - 1] = '\0';
+                               done = 1; /* buffer full, don't wait for more data */
+                       }
+
+                       contentptr = check->bi->data;
+                       contentlen = check->bi->i;
+
+                       /* Check that response body is not empty... */
+                       if (*contentptr == '\0') {
+                               if (!done)
+                                       return;
+
+                               /* empty response */
+                               chunk_printf(&trash, "TCPCHK got an empty response at step %d",
+                                               tcpcheck_get_step_id(s));
+                               set_server_check_status(check, HCHK_STATUS_L7RSP, trash.str);
+
+                               goto out_end_tcpcheck;
+                       }
+
+                       if (!done && (cur->string != NULL) && (check->bi->i < cur->string_len) )
+                               goto wait_more_data;
+
+tcpcheck_expect:
+                       if (cur->string != NULL)
+                               ret = my_memmem(contentptr, contentlen, cur->string, cur->string_len) != NULL;
+                       else if (cur->expect_regex != NULL)
+                               ret = regexec(cur->expect_regex, contentptr, MAX_MATCH, pmatch, 0) == 0;
+
+                       if (!ret && !done)
+                               goto wait_more_data;
+
+                       /* matched */
+                       if (ret) {
+                               /* matched but we did not want to => ERROR */
+                               if (cur->inverse) {
+                                       /* we were looking for a string */
+                                       if (cur->string != NULL) {
+                                               chunk_printf(&trash, "TCPCHK matched unwanted content '%s' at step %d",
+                                                               cur->string, tcpcheck_get_step_id(s));
+                                       }
+                                       else {
+                                       /* we were looking for a regex */
+                                               chunk_printf(&trash, "TCPCHK matched unwanted content (regex) at step %d",
+                                                               tcpcheck_get_step_id(s));
+                                       }
+                                       set_server_check_status(check, HCHK_STATUS_L7RSP, trash.str);
+                                       goto out_end_tcpcheck;
+                               }
+                               /* matched and was supposed to => OK, next step */
+                               else {
+                                       cur = (struct tcpcheck_rule*)cur->list.n;
+                                       check->current_step = cur;
+                                       if (check->current_step->action & TCPCHK_ACT_EXPECT)
+                                               goto tcpcheck_expect;
+                                       __conn_data_stop_recv(conn);
+                               }
+                       }
+                       else {
+                       /* not matched */
+                               /* not matched and was not supposed to => OK, next step */
+                               if (cur->inverse) {
+                                       cur = (struct tcpcheck_rule*)cur->list.n;
+                                       check->current_step = cur;
+                                       if (check->current_step->action & TCPCHK_ACT_EXPECT)
+                                               goto tcpcheck_expect;
+                                       __conn_data_stop_recv(conn);
+                               }
+                               /* not matched but was supposed to => ERROR */
+                               else {
+                                       /* we were looking for a string */
+                                       if (cur->string != NULL) {
+                                               chunk_printf(&trash, "TCPCHK did not match content '%s' at step %d",
+                                                               cur->string, tcpcheck_get_step_id(s));
+                                       }
+                                       else {
+                                       /* we were looking for a regex */
+                                               chunk_printf(&trash, "TCPCHK did not match content (regex) at step %d",
+                                                               tcpcheck_get_step_id(s));
+                                       }
+                                       set_server_check_status(check, HCHK_STATUS_L7RSP, trash.str);
+                                       goto out_end_tcpcheck;
+                               }
+                       }
+               } /* end expect */
+       } /* end loop over double chained step list */
+
+       set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
+       goto out_end_tcpcheck;
+
+ wait_more_data:
+       __conn_data_poll_recv(conn);
+       return;
+
+ out_incomplete:
+       return;
+
+ out_end_tcpcheck:
+       /* collect possible new errors */
+       if (conn->flags & CO_FL_ERROR)
+               chk_report_conn_err(conn, 0, 0);
+
+       /* Close the connection... We absolutely want to perform a hard close
+        * and reset the connection if some data are pending, otherwise we end
+        * up with many TIME_WAITs and eat all the source port range quickly.
+        * To avoid sending RSTs all the time, we first try to drain pending
+        * data.
+        */
+       if (conn->xprt && conn->xprt->shutw)
+               conn->xprt->shutw(conn, 0);
+
+       check->current_step = NULL;
+
+       if (check->result & SRV_CHK_FAILED)
+               conn->flags |= CO_FL_ERROR;
+
+       __conn_data_stop_both(conn);
+       task_wakeup(t, TASK_WOKEN_IO);
+
+       return;
+}
+
+
 /*
  * Local variables:
  *  c-indent-level: 8
index 37bda485aa338115e9d039447d6138ab153f4f5b..f0863d623de1245cacea1fac5846181d1c296f30 100644 (file)
@@ -454,6 +454,7 @@ void init_new_proxy(struct proxy *p)
        LIST_INIT(&p->conf.bind);
        LIST_INIT(&p->conf.listeners);
        LIST_INIT(&p->conf.args.list);
+       LIST_INIT(&p->tcpcheck_rules);
 
        /* Timeouts are defined as -1 */
        proxy_reset_timeouts(p);