MEDIUM: checks: add send/expect tcp based check
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>
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 81b4295..1e71342 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1144,6 +1144,9 @@
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 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 @@
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 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
diff --git a/include/types/checks.h b/include/types/checks.h
index 09a4eee..d7d9725 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -106,4 +106,21 @@
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 */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 8b9f2fb..4883827 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -152,7 +152,8 @@
#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 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 */
diff --git a/include/types/server.h b/include/types/server.h
index 6b73a39..82eb55f 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -122,6 +122,7 @@
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_* */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e267de8..73a08a4 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3732,6 +3732,16 @@
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 @@
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]);
diff --git a/src/checks.c b/src/checks.c
index 56f3537..10aa41a 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -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 @@
{
struct check *check = conn->owner;
const char *err_msg;
+ struct chunk *chk;
if (check->result != SRV_CHK_UNKNOWN)
return;
@@ -850,20 +853,36 @@
* 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 @@
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) {
@@ -985,6 +1009,11 @@
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
@@ -1481,11 +1510,17 @@
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 @@
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
diff --git a/src/proxy.c b/src/proxy.c
index 37bda48..f0863d6 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -454,6 +454,7 @@
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);