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>
This commit is contained in:
Baptiste Assmann 2013-10-06 23:24:13 +02:00 committed by Willy Tarreau
parent bb77c8e26d
commit 5ecb77f4c7
7 changed files with 702 additions and 10 deletions

View 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

View 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 */

View 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 */

View 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_* */

View 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]);

View 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

View 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);