mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-05-09 03:09:28 +00:00
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:
parent
bb77c8e26d
commit
5ecb77f4c7
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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_* */
|
||||
|
165
src/cfgparse.c
165
src/cfgparse.c
@ -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]);
|
||||
|
325
src/checks.c
325
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 @@ 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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user