From: Baptiste Assmann Date: Sun, 6 Oct 2013 21:24:13 +0000 (+0200) Subject: MEDIUM: checks: add send/expect tcp based check X-Git-Tag: v1.5-dev20~151 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5ecb77f;p=thirdparty%2Fhaproxy.git 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 --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 81b42954ef..1e71342577 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 | set-nice | See also : "http-request", section 3.4 about userlists and section 7 about ACL usage. + +tcp-check expect [!] + 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 : + 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. + + 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 : 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 : 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 : 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 + 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 + + : the data to be sent as a question during a generic health check + session. For now, 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 + 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 + + : the data to be sent as a question during a generic health check + session. For now, must be a string. + : 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 [
] Add the server name to a request. Use the header string given by
@@ -3631,6 +3761,75 @@ option forwardfor [ except ] [ header ] [ 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 diff --git a/include/types/checks.h b/include/types/checks.h index 09a4eee013..d7d9725329 100644 --- a/include/types/checks.h +++ b/include/types/checks.h @@ -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 */ diff --git a/include/types/proxy.h b/include/types/proxy.h index 8b9f2fbf5f..4883827613 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.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 */ diff --git a/include/types/server.h b/include/types/server.h index 6b73a39542..82eb55fdb7 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -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_* */ diff --git a/src/cfgparse.c b/src/cfgparse.c index e267de8c83..73a08a42a1 100644 --- a/src/cfgparse.c +++ b/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 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 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 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 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 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 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 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 56f353767d..10aa41ada9 100644 --- a/src/checks.c +++ b/src/checks.c @@ -52,6 +52,8 @@ #include 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 diff --git a/src/proxy.c b/src/proxy.c index 37bda485aa..f0863d623d 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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);