diff --git a/doc/configuration.txt b/doc/configuration.txt index 937f73aa7..997b2a7b9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2653,9 +2653,12 @@ fullconn X - X X grace X X X X hash-type X - X X http-after-response - X X X +http-check connect X - X X http-check disable-on-404 X - X X -http-check expect - - X X +http-check expect X - X X http-check send-state X - X X +http-check set-var X - X X +http-check unset-var X - X X http-request - X X X http-response - X X X http-reuse X - X X @@ -4386,6 +4389,72 @@ http-after-response unset-var() [ { if | unless } ] Example: http-after-response unset-var(sess.last_redir) +http-check connect [params*] + Opens a new connection to perform an HTTP health check + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + + Just like tcp-check health checks, it is possible to configure the connection + to use to perform HTTP health check. This directive should also be used to + describe a scenario involving several request/response exchanges, possibly on + different ports or with different servers. + + When there are no TCP port configured on the server line neither server port + directive, then the first step of the http-check sequence must be to specify + the port with a "http-check connect". + + In an http-check ruleset a 'connect' is required, it is also mandatory to start + the ruleset with a 'connect' rule. Purpose is to ensure admin know what they + do. + + When a connect must start the ruleset, if may still be preceded by set-var, + unset-var or comment rules. + + Parameters : + They are optional and can be used to describe how HAProxy should open and + use the HTTP connection. + + default Use default options of the server line to do the health + checks. The server options are used only if not redifined. + + port if not set, check port or server port is used. + It tells HAProxy where to open the connection to. + must be a valid TCP port source integer, from 1 to + 65535 or an sample-fetch expression. + + addr defines the IP address to do the health check. + + send-proxy send a PROXY protocol string + + via-socks4 enables outgoing health checks using upstream socks4 proxy. + + ssl opens a ciphered connection + + sni specifies the SNI to use to do health checks over SSL. + + alpn defines which protocols to advertise with ALPN. The protocol + list consists in a comma-delimited list of protocol names, + for instance: "h2,http/1.1". If it is not set, the server ALPN + is used. + + linger cleanly close the connection instead of using a single RST. + + Examples: + # check HTTP and HTTPs services on a server. + # first open port 80 thanks to server line port directive, then + # tcp-check opens port 443, ciphered and run a request on it: + option httpchk + + http-check connect + http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu + http-check expect rstatus "^[23][0-9]{2}" + http-check connect port 443 ssl sni haproxy.1wt.eu + http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu + http-check expect rstatus "^[23][0-9]{2}" + + server www 10.0.0.1 check port 80 + + See also : "option httpchk", "http-check send", "http-check expect" http-check disable-on-404 Enable a maintenance mode upon HTTP/404 response to health-checks @@ -4408,11 +4477,58 @@ http-check disable-on-404 See also : "option httpchk", "http-check expect" -http-check expect [!] +http-check expect [min-recv ] + [ok-status ] [error-status ] [tout-status ] + [on-success ] [on-error ] [status-code ] + [!] Make HTTP health checks consider response contents or specific status codes May be used in sections : defaults | frontend | listen | backend yes | no | yes | yes + Arguments : + min-recv is optional and can define the minimum amount of data required to + evaluate the current expect rule. If the number of received bytes + is under this limit, the check will wait for more data. This + option can be used to resolve some ambiguous matching rules or to + avoid executing costly regex matches on content known to be still + incomplete. If an exact string is used, the minimum between the + string length and this parameter is used. This parameter is + ignored if it is set to -1. If the expect rule does not match, + the check will wait for more data. If set to 0, the evaluation + result is always conclusive. + + ok-status is optional and can be used to set the check status if + the expect rule is successfully evaluated and if it is + the last rule in the tcp-check ruleset. "L7OK", "L7OKC", + "L6OK" and "L4OK" are supported and may be used to set, + respectively, HCHK_STATUS_L7OK, HCHK_STATUS_L7OKCD, + HCHK_STATUS_L6OK or HCHK_STATUS_L4OK success status. + By default "L7OK" is used. + + error-status is optional and can be used to set the check status if + an error occurred during the expect rule evaluation. + "L7RSP", "L7STS", "L6RSP" and "L4CON" are supported and + may be used to set, respectively, HCHK_STATUS_L7RSP, + HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP or HCHK_STATUS_L4CON + error status. By default "L7RSP" is used. + + tout-status is optional and can be used to set the check status if + a timeout occurred during the expect rule evaluation. + "L7TOUT", "L6TOUT", and "L4TOUT" are supported and may + be used to set, respectively, HCHK_STATUS_L7TOUT, + HCHK_STATUS_L6TOUT or HCHK_STATUS_L4TOUT timeout status. + By default "L7TOUT" is used. + + on-success is optional and can be used to customize the + informational message reported in logs if the expect + rule is successfully evaluated and if it is the last rule + in the tcp-check ruleset. is a log-format string. + + on-error is optional and can be used to customize the + informational message reported in logs if an error + occurred during the expect rule evaluation. is a + log-format string. + is a keyword indicating how to look for a specific pattern in the response. The keyword may be one of "status", "rstatus", "string", or "rstring". The keyword may be preceded by an @@ -4471,9 +4587,11 @@ http-check expect [!] waste some CPU cycles, especially when regular expressions are used, and that it is always better to focus the checks on smaller resources. - Also "http-check expect" doesn't support HTTP keep-alive. Keep in mind that it - will automatically append a "Connection: close" header, meaning that this - header should not be present in the request provided by "option httpchk". + In an http-check ruleset, the last expect rule may be implicit. If no expect + rule is specified after the last "http-check send", an implicit expect rule + is defined to match on 2xx or 3xx status codes. It means this rule is also + defined if there is no "http-check" rule at all, when only "option httpchk" + is set. Last, if "http-check expect" is combined with "http-check disable-on-404", then this last one has precedence when the server responds with 404. @@ -4491,23 +4609,41 @@ http-check expect [!] # check that we have a correct hexadecimal tag before /html http-check expect rstring - See also : "option httpchk", "http-check disable-on-404" + See also : "option httpchk", "http-check connect", "http-check disable-on-404" + and "http-check send" -http-check send [hdr ]* [body ] +http-check send [meth ] [uri ] [vsn ] + [hdr ]* [body ] Add a possible list of headers and/or a body to the request sent during HTTP health checks. May be used in sections : defaults | frontend | listen | backend yes | no | yes | yes Arguments : - hdr adds the HTTP header field whose name is specified in - and whose value is defined by to the - request sent during HTTP health checks. + meth is the optional HTTP method used with the requests. When not + set, the "OPTIONS" method is used, as it generally requires + low server processing and is easy to filter out from the + logs. Any method may be used, though it is not recommended + to invent non-standard ones. - body add the body defined by to the request sent - sent during HTTP health checks. If defined, the - "Content-Length" header is thus automatically added - to the request. + uri is optional and set the URI referenced in the HTTP requests. + it defaults to " / " which is accessible by default on almost + any server, but may be changed to any other URI. Query + strings are permitted. + + vsn is the optional HTTP version string. It defaults to + "HTTP/1.0" but some servers might behave incorrectly in HTTP + 1.0, so turningit to HTTP/1.1 may sometimes help. Note that + the Host field is mandatory in HTTP/1.1, use "hdr" argument + to add it. + + hdr adds the HTTP header field whose name is specified in + and whose value is defined by , which follows + to the log-format rules. + + body add the body defined by to the request sent during + HTTP health checks. If defined, the "Content-Length" header + is thus automatically added to the request. In addition to the request line defined by the "option httpchk" directive, this one is the valid way to add some headers and optionally a body to the @@ -4518,6 +4654,10 @@ http-check send [hdr ]* [body ] "http-check expect" direcive is defined independently of this directive, just like the state header if the directive "http-check send-state" is defined. + Also "http-check send" doesn't support HTTP keep-alive. Keep in mind that it + will automatically append a "Connection: close" header, meaning that this + header should not be present in the request provided by "http-check send". + See also : "option httpchk", "http-check send-state" and "http-check expect" @@ -4572,6 +4712,50 @@ http-check send-state See also : "option httpchk", "http-check disable-on-404" +http-check set-var() + + This operation sets the content of a variable. The variable is declared inline. + + May be used in sections: defaults | frontend | listen | backend + yes | no | yes | yes + + Arguments: + The name of the variable starts with an indication about its + scope. The scopes allowed for http-check are: + "proc" : the variable is shared with the whole process. + "sess" : the variable is shared with the tcp-check session. + "check": the variable is declared for the lifetime of the tcp-check. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9', '.', + and '-'. + + Is a sample-fetch expression potentially followed by converters. + + Example: + http-check set-var(check.port) int(1234) + + +http-check unset-var() + + Free a reference to a variable within its scope. + + May be used in sections: defaults | frontend | listen | backend + yes | no | yes | yes + + Arguments: + The name of the variable starts with an indication about its + scope. The scopes allowed for http-check are: + "proc" : the variable is shared with the whole process. + "sess" : the variable is shared with the tcp-check session. + "check": the variable is declared for the lifetime of the tcp-check. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9', '.', + and '-'. + + Example: + http-check unset-var(check.port) + + http-request [options...] [ { if | unless } ] Access control for Layer 7 requests @@ -7181,11 +7365,18 @@ option httpchk considered valid, while all other ones indicate a server failure, including the lack of any response. - The port and interval are specified in the server configuration. + Combined with "http-check" directives, it is possible to customize the + request sent during the HTTP health checks or the matching rules on the + response. It is also possible to configure a send/expect sequence, just like + with the directive "tcp-check" for TCP health checks. - This option does not necessarily require an HTTP backend, it also works with - plain TCP backends. This is particularly useful to check simple scripts bound - to some dedicated ports using the inetd daemon. + The server configuration is used by default to open connections to perform + HTTP health checks. By it is also possible to overwrite server parameters + using "http-check connect" rules. + + "httpchk" option does not necessarily require an HTTP backend, it also works + with plain TCP backends. This is particularly useful to check simple scripts + bound to some dedicated ports using the inetd daemon. Note : For a while, there was no way to add headers or body in the request used for HTTP health checks. So a workaround was to hide it at the end diff --git a/include/types/checks.h b/include/types/checks.h index a43efd7c8..f52e4a4d8 100644 --- a/include/types/checks.h +++ b/include/types/checks.h @@ -232,15 +232,40 @@ enum tcpcheck_send_type { TCPCHK_SEND_UNDEF = 0, /* Send is not parsed. */ TCPCHK_SEND_STRING, /* Send an ASCII string. */ TCPCHK_SEND_BINARY, /* Send a binary sequence. */ - TCPCHK_SEND_STRING_LF, /* Send an ASCII log-format string. */ - TCPCHK_SEND_BINARY_LF, /* Send a binary log-format sequence. */ + TCPCHK_SEND_STRING_LF, /* Send an ASCII log-format string. */ + TCPCHK_SEND_BINARY_LF, /* Send a binary log-format sequence. */ + TCPCHK_SEND_HTTP, /* Send an HTTP request */ }; +struct tcpcheck_http_hdr { + struct ist name; /* the header name */ + struct list value; /* the log-format string value */ + struct list list; /* header chained list */ +}; + +#define TCPCHK_SND_HTTP_FL_URI_FMT 0x0001 /* Use a log-format string for the uri */ +#define TCPCHK_SND_HTTP_FL_BODY_FMT 0x0002 /* Use a log-format string for the body */ +#define TCPCHK_SND_HTTP_FROM_OPT 0x0004 /* Send rule coming from "option httpck" directive */ + struct tcpcheck_send { enum tcpcheck_send_type type; union { struct ist data; /* an ASCII string or a binary sequence */ struct list fmt; /* an ASCII or hexa log-format string */ + struct { + unsigned int flags; /* TCPCHK_SND_HTTP_FL_* */ + struct http_meth meth; /* the HTTP request method */ + union { + struct ist uri; /* the HTTP request uri is a string */ + struct list uri_fmt; /* or a log-format string */ + }; + struct ist vsn; /* the HTTP request version string */ + struct list hdrs; /* the HTTP request header list */ + union { + struct ist body; /* the HTTP request payload is a string */ + struct list body_fmt; /* or a log-format string */ + }; + } http; /* Info about the HTTP request to send */ }; }; @@ -251,12 +276,16 @@ enum tcpcheck_eval_ret { }; enum tcpcheck_expect_type { - TCPCHK_EXPECT_UNDEF = 0, /* Match is not used. */ - TCPCHK_EXPECT_STRING, /* Matches a string. */ - TCPCHK_EXPECT_REGEX, /* Matches a regular pattern. */ - TCPCHK_EXPECT_REGEX_BINARY, /* Matches a regular pattern on a hex-encoded text. */ - TCPCHK_EXPECT_BINARY, /* Matches a binary sequence. */ - TCPCHK_EXPECT_CUSTOM, /* Execute a custom function. */ + TCPCHK_EXPECT_UNDEF = 0, /* Match is not used. */ + TCPCHK_EXPECT_STRING, /* Matches a string. */ + TCPCHK_EXPECT_REGEX, /* Matches a regular pattern. */ + TCPCHK_EXPECT_REGEX_BINARY, /* Matches a regular pattern on a hex-encoded text. */ + TCPCHK_EXPECT_BINARY, /* Matches a binary sequence. */ + TCPCHK_EXPECT_CUSTOM, /* Execute a custom function. */ + TCPCHK_EXPECT_HTTP_STATUS, /* Matches a string */ + TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern */ + TCPCHK_EXPECT_HTTP_BODY, /* Matches a string */ + TCPCHK_EXPECT_HTTP_REGEX_BODY, /* Matches a regular pattern */ }; /* tcp-check expect flags */ @@ -275,11 +304,11 @@ struct tcpcheck_expect { }; struct tcpcheck_rule *head; /* first expect of a chain. */ int min_recv; /* Minimum amount of data before an expect can be applied. (default: -1, ignored) */ - struct list onerror_fmt; /* log-format string to use as comment on error */ - struct list onsuccess_fmt; /* log-format string to use as comment on success (if last rule) */ enum healthcheck_status ok_status; /* The healthcheck status to use on success (default: L7OKD) */ enum healthcheck_status err_status; /* The healthcheck status to use on error (default: L7RSP) */ enum healthcheck_status tout_status; /* The healthcheck status to use on timeout (default: L7TOUT) */ + struct list onerror_fmt; /* log-format string to use as comment on error */ + struct list onsuccess_fmt; /* log-format string to use as comment on success (if last rule) */ struct sample_expr *status_expr; /* sample expr to determine the check status code */ }; @@ -311,10 +340,13 @@ struct tcpcheck_rule { #define TCPCHK_RULES_NONE 0x00000000 #define TCPCHK_RULES_UNUSED_TCP_RS 0x00000001 /* An unused tcp-check ruleset exists */ +#define TCPCHK_RULES_UNUSED_HTTP_RS 0x00000002 /* An unused http-check ruleset exists */ +#define TCPCHK_RULES_UNUSED_RS 0x00000003 /* Mask for unused ruleset */ #define TCPCHK_RULES_PGSQL_CHK 0x00000010 #define TCPCHK_RULES_REDIS_CHK 0x00000020 #define TCPCHK_RULES_SMTP_CHK 0x00000030 +#define TCPCHK_RULES_HTTP_CHK 0x00000040 #define TCPCHK_RULES_MYSQL_CHK 0x00000050 #define TCPCHK_RULES_LDAP_CHK 0x00000060 #define TCPCHK_RULES_SSL3_CHK 0x00000070 diff --git a/include/types/proxy.h b/include/types/proxy.h index f9fe7c594..35ca0ad10 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -156,22 +156,10 @@ enum PR_SRV_STATE_FILE { #define PR_O2_SRC_ADDR 0x00100000 /* get the source ip and port for logs */ #define PR_O2_FAKE_KA 0x00200000 /* pretend we do keep-alive with server eventhough we close */ -/* unused : 0x00400000 */ - -#define PR_O2_EXP_NONE 0x00000000 /* http-check : no expect rule */ -#define PR_O2_EXP_STS 0x00800000 /* http-check expect status */ -#define PR_O2_EXP_RSTS 0x01000000 /* http-check expect rstatus */ -#define PR_O2_EXP_STR 0x01800000 /* http-check expect string */ -#define PR_O2_EXP_RSTR 0x02000000 /* http-check expect rstring */ -#define PR_O2_EXP_TYPE 0x03800000 /* mask for http-check expect type */ -#define PR_O2_EXP_INV 0x04000000 /* http-check expect ! */ -/* unused: 0x08000000 */ +/* unused : 0x00400000..0x80000000 */ /* server health checks */ #define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */ -/* unused: 0x10000000..0x30000000 */ -#define PR_O2_HTTP_CHK 0x40000000 /* use HTTP 'OPTIONS' method to check server health */ -/* unused 0x50000000..0x80000000 */ #define PR_O2_TCPCHK_CHK 0x90000000 /* use TCPCHK check for server health */ #define PR_O2_EXT_CHK 0xA0000000 /* use external command for server health */ /* unused: 0xB0000000 to 0xF000000, reserved for health checks */ @@ -418,16 +406,8 @@ struct proxy { struct task *task; /* the associated task, mandatory to manage rate limiting, stopping and resource shortage, NULL if disabled */ struct tcpcheck_rules tcpcheck_rules; /* tcp-check send / expect rules */ int grace; /* grace time after stop request */ - int check_len; /* Length of the HTTP or SSL3 request */ - char *check_req; /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */ - int check_body_len; /* Length of the request body for HTTP checks */ - char *check_hdrs; /* Request headers for HTTP cheks */ - int check_hdrs_len; /* Length of the headers for HTTP checks */ - char *check_body; /* Request body for HTTP cheks */ char *check_command; /* Command to use for external agent checks */ char *check_path; /* PATH environment to use for external agent checks */ - char *expect_str; /* http-check expected content : string or text version of the regex */ - struct my_regex *expect_regex; /* http-check expected content */ struct buffer *errmsg[HTTP_ERR_SIZE]; /* default or customized error messages for known errors */ int uuid; /* universally unique proxy ID, used for SNMP */ unsigned int backlog; /* force the frontend's listen backlog */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 3a7050e45..438accc8c 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -166,7 +166,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) { static struct proxy *curproxy = NULL; const char *err; - char *error; int rc; unsigned val; int err_code = 0; @@ -285,25 +284,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) curproxy->redispatch_after = defproxy.redispatch_after; curproxy->max_ka_queue = defproxy.max_ka_queue; - if (defproxy.check_req) { - curproxy->check_req = calloc(1, defproxy.check_len); - memcpy(curproxy->check_req, defproxy.check_req, defproxy.check_len); - } - curproxy->check_len = defproxy.check_len; - - if (defproxy.check_hdrs) { - curproxy->check_hdrs = calloc(1, defproxy.check_hdrs_len); - memcpy(curproxy->check_hdrs, defproxy.check_hdrs, defproxy.check_hdrs_len); - } - curproxy->check_hdrs_len = defproxy.check_hdrs_len; - - if (defproxy.check_body) { - curproxy->check_body = calloc(1, defproxy.check_body_len); - memcpy(curproxy->check_body, defproxy.check_body, defproxy.check_body_len); - } - curproxy->check_body_len = defproxy.check_body_len; - - curproxy->tcpcheck_rules.flags = (defproxy.tcpcheck_rules.flags & ~TCPCHK_RULES_UNUSED_TCP_RS); + curproxy->tcpcheck_rules.flags = (defproxy.tcpcheck_rules.flags & ~TCPCHK_RULES_UNUSED_RS); curproxy->tcpcheck_rules.list = defproxy.tcpcheck_rules.list; if (!LIST_ISEMPTY(&defproxy.tcpcheck_rules.preset_vars)) { if (!dup_tcpcheck_vars(&curproxy->tcpcheck_rules.preset_vars, @@ -315,21 +296,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } } - if (defproxy.expect_str) { - curproxy->expect_str = strdup(defproxy.expect_str); - if (defproxy.expect_regex) { - /* note: this regex is known to be valid */ - error = NULL; - if (!(curproxy->expect_regex = regex_comp(defproxy.expect_str, 1, 1, &error))) { - ha_alert("parsing [%s:%d] : regular expression '%s' : %s\n", file, linenum, - defproxy.expect_str, error); - free(error); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - } - } - curproxy->ck_opts = defproxy.ck_opts; if (defproxy.cookie_name) curproxy->cookie_name = strdup(defproxy.cookie_name); @@ -502,7 +468,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) goto out; } - free(defproxy.check_req); free(defproxy.check_command); free(defproxy.check_path); free(defproxy.cookie_name); @@ -521,9 +486,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) defproxy.orgto_hdr_len = 0; free(defproxy.server_id_hdr_name); defproxy.server_id_hdr_len = 0; - free(defproxy.expect_str); - regex_free(defproxy.expect_regex); - defproxy.expect_regex = NULL; if (defproxy.conf.logformat_string != default_http_log_format && defproxy.conf.logformat_string != default_tcp_log_format && diff --git a/src/cfgparse.c b/src/cfgparse.c index ae27434c2..e5c9219e1 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2504,7 +2504,14 @@ int check_config_validity() else if (curproxy->options & PR_O_TRANSP) curproxy->options &= ~(PR_O_DISPATCH | PR_O_HTTP_PROXY); - if ((curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_HTTP_CHK) { + if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_HTTP_RS)) { + ha_warning("config : %s '%s' uses http-check rules without 'option httpchk', so the rules are ignored.\n", + proxy_type_str(curproxy), curproxy->id); + err_code |= ERR_WARN; + } + + if ((curproxy->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && + (curproxy->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) { if (curproxy->options & PR_O_DISABLE404) { ha_warning("config : '%s' will be ignored for %s '%s' (requires 'option httpchk').\n", "disable-on-404", proxy_type_str(curproxy), curproxy->id); @@ -3097,8 +3104,7 @@ out_uri_auth_compat: } } - if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS) && - (curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) { + if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS)) { ha_warning("config : %s '%s' uses tcp-check rules without 'option tcp-check', so the rules are ignored.\n", proxy_type_str(curproxy), curproxy->id); err_code |= ERR_WARN; diff --git a/src/checks.c b/src/checks.c index 8ff6bc83c..41927204a 100644 --- a/src/checks.c +++ b/src/checks.c @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include @@ -67,7 +69,6 @@ #include #include -static int httpchk_expect(struct server *s, int done); static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *); static int tcpcheck_main(struct check *); static void __event_srv_chk_w(struct conn_stream *cs); @@ -75,6 +76,12 @@ static int wake_srv_chk(struct conn_stream *cs); static void __event_srv_chk_r(struct conn_stream *cs); static int srv_check_healthcheck_port(struct check *chk); +static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, + struct list *rules, const char *file, int line, + char **errmsg); +static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, + struct list *rules, unsigned int proto, + const char *file, int line, char **errmsg); /* Global list to share all tcp-checks */ struct list tcpchecks_list = LIST_HEAD_INIT(tcpchecks_list); @@ -665,6 +672,18 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired) case TCPCHK_EXPECT_REGEX_BINARY: chunk_appendf(chk, " (expect binary regex)"); break; + case TCPCHK_EXPECT_HTTP_STATUS: + chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr); + break; + case TCPCHK_EXPECT_HTTP_REGEX_STATUS: + chunk_appendf(chk, " (expect HTTP status regex)"); + break; + case TCPCHK_EXPECT_HTTP_BODY: + chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr); + break; + case TCPCHK_EXPECT_HTTP_REGEX_BODY: + chunk_appendf(chk, " (expect HTTP body regex)"); + break; case TCPCHK_EXPECT_CUSTOM: chunk_appendf(chk, " (expect custom function)"); break; @@ -854,9 +873,7 @@ static void __event_srv_chk_r(struct conn_stream *cs) { struct connection *conn = cs->conn; struct check *check = cs->data; - struct server *s = check->server; struct task *t = check->task; - char *desc; int done; if (unlikely(check->result == CHK_RES_FAILED)) @@ -907,58 +924,14 @@ static void __event_srv_chk_r(struct conn_stream *cs) } /* Run the checks... */ - switch (check->type) { - case PR_O2_HTTP_CHK: - if (!done && b_data(&check->bi) < strlen("HTTP/1.0 000\r")) - goto wait_more_data; - /* Check if the server speaks HTTP 1.X */ - if ((b_data(&check->bi) < strlen("HTTP/1.0 000\r")) || - (memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 || - (*(b_head(&check->bi) + 12) != ' ' && *(b_head(&check->bi) + 12) != '\r')) || - !isdigit((unsigned char) *(b_head(&check->bi) + 9)) || !isdigit((unsigned char) *(b_head(&check->bi) + 10)) || - !isdigit((unsigned char) *(b_head(&check->bi) + 11))) { - cut_crlf(b_head(&check->bi)); - set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi)); - - goto out_wakeup; - } - - check->code = str2uic(b_head(&check->bi) + 9); - desc = ltrim(b_head(&check->bi) + 12, ' '); - - if ((s->proxy->options & PR_O_DISABLE404) && - (s->next_state != SRV_ST_STOPPED) && (check->code == 404)) { - /* 404 may be accepted as "stopping" only if the server was up */ - cut_crlf(desc); - set_server_check_status(check, HCHK_STATUS_L7OKCD, desc); - } - else if (s->proxy->options2 & PR_O2_EXP_TYPE) { - /* Run content verification check... We know we have at least 13 chars */ - if (!httpchk_expect(s, done)) - goto wait_more_data; - } - /* check the reply : HTTP/1.X 2xx and 3xx are OK */ - else if (*(b_head(&check->bi) + 9) == '2' || *(b_head(&check->bi) + 9) == '3') { - cut_crlf(desc); - set_server_check_status(check, HCHK_STATUS_L7OKD, desc); - } - else { - cut_crlf(desc); - set_server_check_status(check, HCHK_STATUS_L7STS, desc); - } - break; - - default: - /* good connection is enough for pure TCP check */ - if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) { - if (check->use_ssl == 1) - set_server_check_status(check, HCHK_STATUS_L6OK, NULL); - else - set_server_check_status(check, HCHK_STATUS_L4OK, NULL); - } - break; - } /* switch */ + /* good connection is enough for pure TCP check */ + if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) { + if (check->use_ssl == 1) + set_server_check_status(check, HCHK_STATUS_L6OK, NULL); + else + set_server_check_status(check, HCHK_STATUS_L4OK, NULL); + } out_wakeup: /* collect possible new errors */ @@ -1188,47 +1161,6 @@ static int connect_conn_chk(struct task *t) if (conn) return SF_ERR_INTERNAL; - /* 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->state & CHK_ST_AGENT)) { - b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len); - - /* we want to check if this host replies to HTTP requests - * so we'll send the request, and won't wake the checker up now. - */ - if ((check->type) == PR_O2_HTTP_CHK) { - /* prevent HTTP keep-alive when "http-check expect" is used */ - if (s->proxy->options2 & PR_O2_EXP_TYPE) - b_putist(&check->bo, ist("Connection: close\r\n")); - - /* If there is a body, add its content-length */ - if (s->proxy->check_body_len) - chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len)); - - /* Add configured headers */ - if (s->proxy->check_hdrs) - b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len); - - /* Add send-state header */ - if (s->proxy->options2 & PR_O2_CHK_SNDST) - b_putblk(&check->bo, trash.area, - httpchk_build_status_header(s, trash.area, trash.size)); - - /* end-of-header */ - b_putist(&check->bo, ist("\r\n")); - - /* Add the body */ - if (s->proxy->check_body) - b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len); - - *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */ - } - } - /* for tcp-checks, the initial connection setup is handled separately as * it may be sent to a specific port and not to the server's. */ @@ -2194,110 +2126,6 @@ static int start_checks() return 0; } -/* - * Perform content verification check on data in s->check.buffer buffer. - * The buffer MUST be terminated by a null byte before calling this function. - * Sets server status appropriately. The caller is responsible for ensuring - * that the buffer contains at least 13 characters. If is zero, we may - * return 0 to indicate that data is required to decide of a match. - */ -static int httpchk_expect(struct server *s, int done) -{ - static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>"; - char status_code[] = "000"; - char *contentptr; - int crlf; - int ret; - - switch (s->proxy->options2 & PR_O2_EXP_TYPE) { - case PR_O2_EXP_STS: - case PR_O2_EXP_RSTS: - memcpy(status_code, b_head(&s->check.bi) + 9, 3); - memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3); - - if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS) - ret = strncmp(s->proxy->expect_str, status_code, 3) == 0; - else - ret = regex_exec(s->proxy->expect_regex, status_code); - - /* we necessarily have the response, so there are no partial failures */ - if (s->proxy->options2 & PR_O2_EXP_INV) - ret = !ret; - - set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg); - break; - - case PR_O2_EXP_STR: - case PR_O2_EXP_RSTR: - /* very simple response parser: ignore CR and only count consecutive LFs, - * stop with contentptr pointing to first char after the double CRLF or - * to '\0' if crlf < 2. - */ - crlf = 0; - for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) { - if (crlf >= 2) - break; - if (*contentptr == '\r') - continue; - else if (*contentptr == '\n') - crlf++; - else - crlf = 0; - } - - /* Check that response contains a body... */ - if (crlf < 2) { - if (!done) - return 0; - - set_server_check_status(&s->check, HCHK_STATUS_L7RSP, - "HTTP content check could not find a response body"); - return 1; - } - - /* Check that response body is not empty... */ - if (*contentptr == '\0') { - if (!done) - return 0; - - set_server_check_status(&s->check, HCHK_STATUS_L7RSP, - "HTTP content check found empty response body"); - return 1; - } - - /* Check the response content against the supplied string - * or regex... */ - if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR) - ret = strstr(contentptr, s->proxy->expect_str) != NULL; - else - ret = regex_exec(s->proxy->expect_regex, contentptr); - - /* if we don't match, we may need to wait more */ - if (!ret && !done) - return 0; - - if (ret) { - /* content matched */ - if (s->proxy->options2 & PR_O2_EXP_INV) - set_server_check_status(&s->check, HCHK_STATUS_L7RSP, - "HTTP check matched unwanted content"); - else - set_server_check_status(&s->check, HCHK_STATUS_L7OKD, - "HTTP content check matched"); - } - else { - if (s->proxy->options2 & PR_O2_EXP_INV) - set_server_check_status(&s->check, HCHK_STATUS_L7OKD, - "HTTP check did not match unwanted content"); - else - set_server_check_status(&s->check, HCHK_STATUS_L7RSP, - "HTTP content check did not match"); - } - break; - } - return 1; -} - /* * return the id of a step in a send/expect session */ @@ -2339,6 +2167,8 @@ static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, st chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content")); switch (rule->expect.type) { case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_HTTP_STATUS: + case TCPCHK_EXPECT_HTTP_BODY: chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), rule->expect.data.ptr, tcpcheck_get_step_id(check, rule)); break; @@ -2346,6 +2176,8 @@ static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, st chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule)); break; case TCPCHK_EXPECT_REGEX: + case TCPCHK_EXPECT_HTTP_REGEX_STATUS: + case TCPCHK_EXPECT_HTTP_REGEX_BODY: chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule)); break; case TCPCHK_EXPECT_REGEX_BINARY: @@ -3082,6 +2914,54 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0) goto error_lf; break; + case TCPCHK_SEND_HTTP: { + struct ist meth, uri, vsn; + + meth = ((send->http.meth.meth == HTTP_METH_OTHER) + ? ist2(send->http.meth.str.area, send->http.meth.str.data) + : http_known_methods[send->http.meth.meth]); + uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt + vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0")); + + chunk_istcat(&check->bo, meth); + check->bo.area[check->bo.data++] = ' '; + chunk_istcat(&check->bo, uri); + check->bo.area[check->bo.data++] = ' '; + chunk_istcat(&check->bo, vsn); + chunk_istcat(&check->bo, ist("\r\n")); + chunk_istcat(&check->bo, ist("Connection: close\r\n")); + if (isttest(send->http.body)) { + // TODO: handle body_fmt + chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(istlen(send->http.body))); + } + if (check->proxy->options2 & PR_O2_CHK_SNDST) { + trash.data = httpchk_build_status_header(check->server, b_orig(&trash), b_size(&trash)); + chunk_cat(&check->bo, &trash); + } + if (!LIST_ISEMPTY(&send->http.hdrs)) { + struct tcpcheck_http_hdr *hdr; + + tmp = alloc_trash_chunk(); + if (!tmp) + goto error_lf; + list_for_each_entry(hdr, &send->http.hdrs, list) { + chunk_reset(tmp); + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value); + if (!b_data(tmp)) + continue; + chunk_istcat(&check->bo, hdr->name); + check->bo.area[check->bo.data++] = ' '; + chunk_cat(&check->bo, tmp); + chunk_istcat(&check->bo, ist("\r\n")); + } + } + chunk_istcat(&check->bo, ist("\r\n")); + if (isttest(send->http.body)) { + // TODO: handle body_fmt + chunk_istcat(&check->bo, send->http.body); + } + break; + } case TCPCHK_SEND_UNDEF: /* Should never happen. */ ret = TCPCHK_EVAL_STOP; @@ -3113,6 +2993,130 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp } +static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + struct tcpcheck_expect *expect = &rule->expect; + struct buffer *msg = NULL; + enum healthcheck_status status; + struct ist desc = ist(NULL); + char *body; + size_t body_len; + int match, inverse; + + last_read |= b_full(&check->bi); + + /* Must at least receive the status line (HTTP/1.X XXX.) */ + if (!last_read && b_data(&check->bi) < 13) + goto wait_more_data; + + /* Check if the server speaks HTTP 1.X */ + if (b_data(&check->bi) < 13 || + memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 || + (*b_peek(&check->bi, 12) != ' ' && *b_peek(&check->bi, 12) != '\r') || + !isdigit((unsigned char) *b_peek(&check->bi, 9)) || !isdigit((unsigned char) *b_peek(&check->bi, 10)) || + !isdigit((unsigned char) *b_peek(&check->bi, 11))) { + status = HCHK_STATUS_L7RSP; + desc = ist2(b_head(&check->bi), my_memcspn(b_head(&check->bi), b_data(&check->bi), "\r\n", 2)); + goto error; + } + + check->code = strl2uic(b_peek(&check->bi, 9), 3); + + if (check->server && + (check->server->proxy->options & PR_O_DISABLE404) && + (check->server->next_state != SRV_ST_STOPPED) && + (check->code == 404)) { + /* 404 may be accepted as "stopping" only if the server was up */ + goto out; + } + + inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV); + /* Make GCC happy ; initialize match to a failure state. */ + match = inverse; + + switch (expect->type) { + case TCPCHK_EXPECT_HTTP_STATUS: + match = my_memmem(b_peek(&check->bi, 9), 3, expect->data.ptr, istlen(expect->data)) != NULL; + + /* Set status and description in case of error */ + status = HCHK_STATUS_L7STS; + desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2)); + break; + case TCPCHK_EXPECT_HTTP_REGEX_STATUS: + match = regex_exec2(expect->regex, b_peek(&check->bi, 9), 3); + + /* Set status and description in case of error */ + status = HCHK_STATUS_L7STS; + desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2)); + break; + case TCPCHK_EXPECT_HTTP_BODY: + case TCPCHK_EXPECT_HTTP_REGEX_BODY: + body = (char *)my_memmem(b_head(&check->bi), b_data(&check->bi), "\r\n\r\n", 4); + if (!body) { + if (!last_read) + goto wait_more_data; + + status = HCHK_STATUS_L7RSP; + desc = ist("HTTP content check could not find a response body"); + goto error; + } + body += 4; + body_len = b_tail(&check->bi) - body; + + if (!last_read && + ((expect->type == TCPCHK_EXPECT_HTTP_BODY && body_len < istlen(expect->data)) || + (expect->min_recv > 0 && body_len < expect->min_recv))) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + + if (expect->type ==TCPCHK_EXPECT_HTTP_BODY) + match = my_memmem(body, body_len, expect->data.ptr, istlen(expect->data)) != NULL; + else + match = regex_exec2(expect->regex, body, body_len); + + /* Set status and description in case of error */ + status = HCHK_STATUS_L7RSP; + desc = (inverse + ? ist("HTTP check matched unwanted content") + : ist("HTTP content check did not match")); + break; + + default: + /* should never happen */ + status = HCHK_STATUS_L7RSP; + goto error; + } + + /* Wait for more data on mismatch only if no minimum is defined (-1), + * otherwise the absence of match is already conclusive. + */ + if (!match && !last_read && (expect->min_recv == -1)) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + + if (!(match ^ inverse)) + goto error; + + out: + free_trash_chunk(msg); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_onerror_message(msg, check, rule, 0, desc); + set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); + goto out; + + wait_more_data: + ret = TCPCHK_EVAL_WAIT; + goto out; +} + /* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0 * to wait and -1 to stop the check. */ @@ -3123,6 +3127,8 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t struct buffer *msg = NULL; int match, inverse; + last_read |= b_full(&check->bi); + /* The current expect might need more data than the previous one, check again * that the minimum amount data required to match is respected. */ @@ -3168,7 +3174,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t if (expect->custom) ret = expect->custom(check, rule, last_read); goto out; - case TCPCHK_EXPECT_UNDEF: + default: /* Should never happen. */ ret = TCPCHK_EVAL_STOP; goto out; @@ -3306,6 +3312,7 @@ static int tcpcheck_main(struct check *check) list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) { enum tcpcheck_eval_ret eval_ret; + check->code = 0; switch (rule->action) { case TCPCHK_ACT_CONNECT: check->current_step = rule; @@ -3352,10 +3359,6 @@ static int tcpcheck_main(struct check *check) } } - /* buffer full, don't wait for more data */ - if (b_full(&check->bi)) - last_read = 1; - /* Check that response body is not empty... */ if (!b_data(&check->bi)) { if (!last_read) @@ -3373,7 +3376,10 @@ static int tcpcheck_main(struct check *check) must_read = 0; } - eval_ret = tcpcheck_eval_expect(check, rule, last_read); + eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK + ? tcpcheck_eval_expect_http(check, rule, last_read) + : tcpcheck_eval_expect(check, rule, last_read)); + if (eval_ret == TCPCHK_EVAL_WAIT) { check->current_step = rule->expect.head; conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); @@ -3406,8 +3412,17 @@ static int tcpcheck_main(struct check *check) rule = check->current_step; if (rule->action == TCPCHK_ACT_EXPECT) { - struct buffer *msg = alloc_trash_chunk(); + struct buffer *msg; + if (check->server && + (check->server->proxy->options & PR_O_DISABLE404) && + (check->server->next_state != SRV_ST_STOPPED) && + (check->code == 404)) { + set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL); + goto out_end_tcpcheck; + } + + msg = alloc_trash_chunk(); if (msg) tcpcheck_onsuccess_message(msg, check, rule, ist(NULL)); set_server_check_status(check, rule->expect.ok_status, @@ -3477,10 +3492,40 @@ void free_check(struct check *check) } } -static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) +static void free_tcpcheck_fmt(struct list *fmt) { struct logformat_node *lf, *lfb; + list_for_each_entry_safe(lf, lfb, fmt, list) { + LIST_DEL(&lf->list); + release_sample_expr(lf->expr); + free(lf->arg); + free(lf); + } +} + +static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr) +{ + if (!hdr) + return; + + free_tcpcheck_fmt(&hdr->value); + free(hdr->name.ptr); + free(hdr); +} + +static void free_tcpcheck_http_hdrs(struct list *hdrs) +{ + struct tcpcheck_http_hdr *hdr, *bhdr; + + list_for_each_entry_safe(hdr, bhdr, hdrs, list) { + LIST_DEL(&hdr->list); + free_tcpcheck_http_hdr(hdr); + } +} + +static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) +{ if (!rule) return; @@ -3494,38 +3539,40 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) break; case TCPCHK_SEND_STRING_LF: case TCPCHK_SEND_BINARY_LF: - list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) { - LIST_DEL(&lf->list); - release_sample_expr(lf->expr); - free(lf->arg); - free(lf); - } + free_tcpcheck_fmt(&rule->send.fmt); + break; + case TCPCHK_SEND_HTTP: + free(rule->send.http.meth.str.area); + if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT)) + free(rule->send.http.uri.ptr); + else + free_tcpcheck_fmt(&rule->send.http.uri_fmt); + free(rule->send.http.vsn.ptr); + free_tcpcheck_http_hdrs(&rule->send.http.hdrs); + if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT)) + free(rule->send.http.body.ptr); + else + free_tcpcheck_fmt(&rule->send.http.body_fmt); break; case TCPCHK_SEND_UNDEF: break; } break; case TCPCHK_ACT_EXPECT: - list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) { - LIST_DEL(&lf->list); - release_sample_expr(lf->expr); - free(lf->arg); - free(lf); - } - list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) { - LIST_DEL(&lf->list); - release_sample_expr(lf->expr); - free(lf->arg); - free(lf); - } + free_tcpcheck_fmt(&rule->expect.onerror_fmt); + free_tcpcheck_fmt(&rule->expect.onsuccess_fmt); release_sample_expr(rule->expect.status_expr); switch (rule->expect.type) { case TCPCHK_EXPECT_STRING: case TCPCHK_EXPECT_BINARY: + case TCPCHK_EXPECT_HTTP_STATUS: + case TCPCHK_EXPECT_HTTP_BODY: free(rule->expect.data.ptr); break; case TCPCHK_EXPECT_REGEX: case TCPCHK_EXPECT_REGEX_BINARY: + case TCPCHK_EXPECT_HTTP_REGEX_STATUS: + case TCPCHK_EXPECT_HTTP_REGEX_BODY: regex_free(rule->expect.regex); break; case TCPCHK_EXPECT_CUSTOM: @@ -4025,12 +4072,18 @@ REGISTER_POST_CHECK(start_checks); static int check_proxy_tcpcheck(struct proxy *px) { struct tcpcheck_rule *chk, *back; - char *comment = NULL; + char *comment = NULL, *errmsg = NULL; enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT; int ret = 0; - if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) + if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) { + deinit_proxy_tcpcheck(px); goto out; + } + + free(px->check_command); + free(px->check_path); + px->check_command = px->check_path = NULL; if (!px->tcpcheck_rules.list) { ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id); @@ -4038,6 +4091,39 @@ static int check_proxy_tcpcheck(struct proxy *px) goto out; } + /* HTTP ruleset */ + if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) { + struct tcpcheck_rule *next; + + /* move remaining send rule from "option httpchk" line to the right place */ + chk = get_first_tcpcheck_rule(&px->tcpcheck_rules); + if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) { + next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk); + if (next && next->action == TCPCHK_ACT_CONNECT) { + LIST_DEL(&chk->list); + LIST_ADD(&next->list, &chk->list); + chk->index = next->index; + } + } + + /* add implicit expect rule if the last one is a send. */ + chk = get_last_tcpcheck_rule(&px->tcpcheck_rules); + if (chk && chk->action == TCPCHK_ACT_SEND) { + next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""}, + 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK, + px->conf.file, px->conf.line, &errmsg); + if (!next) { + ha_alert("config : proxy '%s': unable to add implicit http-check expect rule " + "(%s).\n", px->id, errmsg); + free(errmsg); + ret |= ERR_ALERT | ERR_FATAL; + goto out; + } + LIST_ADDQ(px->tcpcheck_rules.list, &next->list); + next->index = chk->index; + } + } + /* If there is no connect rule preceeding all send / expect rules, an * implicit one is inserted before all others */ @@ -4420,10 +4506,13 @@ static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, st int alpn_len = 0; list_for_each_entry(chk, rules, list) { - if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW) + if (chk->action == TCPCHK_ACT_CONNECT) break; - } - if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) { + if (chk->action == TCPCHK_ACT_COMMENT || + chk->action == TCPCHK_ACT_ACTION_KW || + (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))) + continue; + memprintf(errmsg, "first step MUST also be a 'connect', " "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', " "when there is a 'connect' step in the tcp-check ruleset"); @@ -4669,6 +4758,7 @@ static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struc goto error; } break; + case TCPCHK_SEND_HTTP: case TCPCHK_SEND_UNDEF: goto error; } @@ -4681,6 +4771,283 @@ static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struc return NULL; } +static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + struct tcpcheck_http_hdr *hdr = NULL; + struct http_hdr hdrs[global.tune.max_http_hdr]; + char *meth = NULL, *uri = NULL, *vsn = NULL; + char *body = NULL, *comment = NULL; + unsigned int flags = 0; + int i = 0; + + cur_arg++; + while (*(args[cur_arg])) { + if (strcmp(args[cur_arg], "meth") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + meth = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "uri") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + uri = args[cur_arg]; + // TODO: log-format uri + } + else if (strcmp(args[cur_arg], "vsn") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + vsn = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "hdr") == 0) { + if (!*args[cur_arg+1] || !*args[cur_arg+2]) { + memprintf(errmsg, "'%s' expects and as arguments", args[cur_arg]); + goto error; + } + hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1])); + hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2])); + i++; + cur_arg += 2; + } + else if (strcmp(args[cur_arg], "body") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + body = args[cur_arg]; + // TODO: log-format body + } + else if (strcmp(args[cur_arg], "comment") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + free(comment); + comment = strdup(args[cur_arg]); + if (!comment) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + else { + memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.", + args[cur_arg]); + goto error; + } + cur_arg++; + } + + hdrs[i].n = hdrs[i].v = IST_NULL; + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_SEND; + chk->comment = comment; comment = NULL; + chk->send.type = TCPCHK_SEND_HTTP; + chk->send.http.flags = flags; + LIST_INIT(&chk->send.http.hdrs); + + if (meth) { + chk->send.http.meth.meth = find_http_meth(meth, strlen(meth)); + chk->send.http.meth.str.area = strdup(meth); + chk->send.http.meth.str.data = strlen(meth); + if (!chk->send.http.meth.str.area) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + if (uri) { + chk->send.http.uri = ist2(strdup(uri), strlen(uri)); + if (!isttest(chk->send.http.uri)) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + if (vsn) { + chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn)); + if (!isttest(chk->send.http.vsn)) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + for (i = 0; hdrs[i].n.len; i++) { + hdr = calloc(1, sizeof(*hdr)); + if (!hdr) { + memprintf(errmsg, "out of memory"); + goto error; + } + LIST_INIT(&hdr->value); + hdr->name = ist2(strdup(hdrs[i].n.ptr), hdrs[i].n.len); + if (!hdr->name.ptr) { + memprintf(errmsg, "out of memory"); + goto error; + } + + hdrs[i].v.ptr[hdrs[i].v.len] = '\0'; + if (!parse_logformat_string(hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg)) + goto error; + LIST_ADDQ(&chk->send.http.hdrs, &hdr->list); + hdr = NULL; + } + + if (body) { + chk->send.http.body = ist2(strdup(body), strlen(body)); + if (!isttest(chk->send.http.body)) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + + return chk; + + error: + free_tcpcheck_http_hdr(hdr); + free_tcpcheck(chk, 0); + free(comment); + return NULL; +} + +static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new) +{ + struct logformat_node *lf, *lfb; + struct tcpcheck_http_hdr *hdr, *bhdr; + + + if (new->send.http.meth.str.area) { + free(old->send.http.meth.str.area); + old->send.http.meth.meth = new->send.http.meth.meth; + old->send.http.meth.str.area = new->send.http.meth.str.area; + old->send.http.meth.str.data = new->send.http.meth.str.data; + new->send.http.meth.str = BUF_NULL; + } + + if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) { + if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT)) + free(old->send.http.uri.ptr); + else + free_tcpcheck_fmt(&old->send.http.uri_fmt); + old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT; + old->send.http.uri = new->send.http.uri; + new->send.http.uri = IST_NULL; + } + else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) { + if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT)) + free(old->send.http.uri.ptr); + else + free_tcpcheck_fmt(&old->send.http.uri_fmt); + old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT; + LIST_INIT(&old->send.http.uri_fmt); + list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) { + LIST_DEL(&lf->list); + LIST_ADDQ(&old->send.http.uri_fmt, &lf->list); + } + } + + if (isttest(new->send.http.vsn)) { + free(old->send.http.vsn.ptr); + old->send.http.vsn = new->send.http.vsn; + new->send.http.vsn = IST_NULL; + } + + free_tcpcheck_http_hdrs(&old->send.http.hdrs); + list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) { + LIST_DEL(&hdr->list); + LIST_ADDQ(&old->send.http.hdrs, &hdr->list); + } + + if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) { + if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT)) + free(old->send.http.body.ptr); + else + free_tcpcheck_fmt(&old->send.http.body_fmt); + old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT; + old->send.http.body = new->send.http.body; + new->send.http.body = IST_NULL; + } + else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) { + if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT)) + free(old->send.http.body.ptr); + else + free_tcpcheck_fmt(&old->send.http.body_fmt); + old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT; + LIST_INIT(&old->send.http.body_fmt); + list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) { + LIST_DEL(&lf->list); + LIST_ADDQ(&old->send.http.body_fmt, &lf->list); + } + } +} + +static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg) +{ + struct tcpcheck_rule *r; + + if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) { + r = get_first_tcpcheck_rule(rules); + if (r && r->action == TCPCHK_ACT_CONNECT) + r = get_next_tcpcheck_rule(rules, r); + if (!r || r->action != TCPCHK_ACT_SEND) + LIST_ADD(rules->list, &chk->list); + else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) { + LIST_DEL(&r->list); + free_tcpcheck(r, 0); + LIST_ADD(rules->list, &chk->list); + } + else { + tcpcheck_overwrite_send_http_rule(r, chk); + free_tcpcheck(chk, 0); + } + } + else { + r = get_last_tcpcheck_rule(rules); + if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))) + /* no error */; + else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) { + memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).", + chk->index+1); + return 0; + } + else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) { + memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).", + chk->index+1); + return 0; + } + else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) { + memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).", + chk->index+1); + return 0; + } + + if (chk->action == TCPCHK_ACT_SEND) { + r = get_first_tcpcheck_rule(rules); + if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) { + tcpcheck_overwrite_send_http_rule(r, chk); + free_tcpcheck(chk, 0); + LIST_DEL(&r->list); + r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT; + chk = r; + } + } + LIST_ADDQ(rules->list, &chk->list); + } + return 1; +} + static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules, const char *file, int line, char **errmsg) { @@ -4712,7 +5079,8 @@ static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, st return NULL; } -static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules, +static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, + struct list *rules, unsigned int proto, const char *file, int line, char **errmsg) { struct tcpcheck_rule *prev_check, *chk = NULL; @@ -4763,15 +5131,47 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str cur_arg++; goto rescan; } - else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 || - strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) { + else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) { if (type != TCPCHK_EXPECT_UNDEF) { memprintf(errmsg, "only on pattern expected"); goto error; } - type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : - ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : - ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY))); + if (proto != TCPCHK_RULES_HTTP_CHK) + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX); + else + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY); + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a as argument", args[cur_arg]); + goto error; + } + cur_arg++; + pattern = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) { + if (proto == TCPCHK_RULES_HTTP_CHK) + goto bad_http_kw; + if (type != TCPCHK_EXPECT_UNDEF) { + memprintf(errmsg, "only on pattern expected"); + goto error; + } + type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY); + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a as argument", args[cur_arg]); + goto error; + } + cur_arg++; + pattern = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) { + if (proto != TCPCHK_RULES_HTTP_CHK) + goto bad_tcp_kw; + if (type != TCPCHK_EXPECT_UNDEF) { + memprintf(errmsg, "only on pattern expected"); + goto error; + } + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS); if (!*(args[cur_arg+1])) { memprintf(errmsg, "'%s' expects a as argument", args[cur_arg]); @@ -4943,8 +5343,16 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str cur_arg++; } else { - memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'" - " or comment but got '%s' as argument.", args[cur_arg]); + if (proto == TCPCHK_RULES_HTTP_CHK) { + bad_http_kw: + memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'" + " or comment but got '%s' as argument.", args[cur_arg]); + } + else { + bad_tcp_kw: + memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'" + " or comment but got '%s' as argument.", args[cur_arg]); + } goto error; } @@ -5009,6 +5417,8 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str switch (chk->expect.type) { case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_HTTP_STATUS: + case TCPCHK_EXPECT_HTTP_BODY: chk->expect.data = ist2(strdup(pattern), strlen(pattern)); if (!chk->expect.data.ptr) { memprintf(errmsg, "out of memory"); @@ -5022,6 +5432,8 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str } case TCPCHK_EXPECT_REGEX: case TCPCHK_EXPECT_REGEX_BINARY: + case TCPCHK_EXPECT_HTTP_REGEX_STATUS: + case TCPCHK_EXPECT_HTTP_REGEX_BODY: chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg); if (!chk->expect.regex) goto error; @@ -5098,7 +5510,7 @@ static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx, else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0) chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg); else if (strcmp(args[cur_arg], "expect") == 0) - chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, file, line, errmsg); + chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg); else if (strcmp(args[cur_arg], "comment") == 0) chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg); else { @@ -5148,7 +5560,9 @@ static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx, struct proxy *defpx, const char *file, int line, char **errmsg) { - int cur_arg, ret = 0; + struct tcpcheck_ruleset *rs = NULL; + struct tcpcheck_rule *chk = NULL; + int index, cur_arg, ret = 0; if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL)) ret = 1; @@ -5159,156 +5573,89 @@ static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx, curpx->options |= PR_O_DISABLE404; if (too_many_args(1, args, errmsg, NULL)) goto error; + goto out; } else if (strcmp(args[cur_arg], "send-state") == 0) { /* enable emission of the apparent state of a server in HTTP checks */ curpx->options2 |= PR_O2_CHK_SNDST; if (too_many_args(1, args, errmsg, NULL)) goto error; + goto out; } - else if (strcmp(args[cur_arg], "send") == 0) { - free(curpx->check_hdrs); - free(curpx->check_body); - curpx->check_hdrs = curpx->check_body = NULL; - curpx->check_hdrs_len = curpx->check_body_len = 0; - cur_arg++; - while (*(args[cur_arg])) { - if (strcmp(args[cur_arg], "hdr") == 0) { - int hdr_len; - if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) { - memprintf(errmsg, "'%s %s' : %s expects a name and a value as parameter.", - args[0], args[1], args[cur_arg]); - goto error; - } + /* Deduce the ruleset name from the proxy info */ + chunk_printf(&trash, "*http-check-%s_%s-%d", + ((curpx == defpx) ? "defaults" : curpx->id), + curpx->conf.file, curpx->conf.line); - cur_arg++; - hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4; - curpx->check_hdrs = my_realloc2(curpx->check_hdrs, curpx->check_hdrs_len+hdr_len+1); - if (curpx->check_hdrs == NULL) { - memprintf(errmsg, "out of memory."); - goto error; - } - snprintf(curpx->check_hdrs + curpx->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]); - curpx->check_hdrs_len += hdr_len; - - cur_arg++; - } - else if (strcmp(args[cur_arg], "body") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s %s' : %s expects a string as parameter.", - args[0], args[1], args[cur_arg]); - goto error; - } - cur_arg++; - free(curpx->check_body); - curpx->check_body = strdup(args[cur_arg]); - curpx->check_body_len = strlen(args[cur_arg]); - if (curpx->check_body == NULL) { - memprintf(errmsg, "out of memory."); - goto error; - } - } - else { - memprintf(errmsg, "'%s %s' only supports 'hdr' and 'body', found '%s'.", - args[0], args[1], args[cur_arg]); - goto error; - } - cur_arg++; - } - } - else if (strcmp(args[cur_arg], "expect") == 0) { - const char *ptr_arg; - - if (curpx->options2 & PR_O2_EXP_TYPE) { - memprintf(errmsg, "'%s %s' already specified.", args[0], args[1]); + rs = tcpcheck_ruleset_lookup(b_orig(&trash)); + if (rs == NULL) { + rs = tcpcheck_ruleset_create(b_orig(&trash)); + if (rs == NULL) { + memprintf(errmsg, "out of memory.\n"); goto error; } + } - cur_arg++; + index = 0; + if (!LIST_ISEMPTY(&rs->rules)) { + chk = LIST_PREV(&rs->rules, typeof(chk), list); + if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) + index = chk->index + 1; + } - /* consider exclamation marks, sole or at the beginning of a word */ - while (*(ptr_arg = args[cur_arg])) { - while (*ptr_arg == '!') { - curpx->options2 ^= PR_O2_EXP_INV; - ptr_arg++; - } - if (*ptr_arg) - break; - cur_arg++; - } + if (strcmp(args[cur_arg], "connect") == 0) + chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg); + else if (strcmp(args[cur_arg], "send") == 0) + chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg); + else if (strcmp(args[cur_arg], "expect") == 0) + chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK, + file, line, errmsg); + else if (strcmp(args[cur_arg], "comment") == 0) + chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg); + else { + struct action_kw *kw = action_kw_tcp_check_lookup(args[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, "status") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s %s %s' expects as an argument.", - args[0], args[1], ptr_arg); - goto error; - } - curpx->options2 |= PR_O2_EXP_STS; - free(curpx->expect_str); - curpx->expect_str = strdup(args[cur_arg+1]); + if (!kw) { + action_kw_tcp_check_build_list(&trash); + memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect'," + " 'send', 'expect'%s%s. but got '%s'", + args[0], (*trash.area ? ", " : ""), trash.area, args[1]); + goto error; } - else if (strcmp(ptr_arg, "string") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s %s %s' expects as an argument.", - args[0], args[1], ptr_arg); - goto error; - } - curpx->options2 |= PR_O2_EXP_STR; - free(curpx->expect_str); - curpx->expect_str = strdup(args[cur_arg+1]); - } - else if (strcmp(ptr_arg, "rstatus") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s %s %s' expects as an argument.", - args[0], args[1], ptr_arg); - goto error; - } - curpx->options2 |= PR_O2_EXP_RSTS; - free(curpx->expect_str); - regex_free(curpx->expect_regex); - curpx->expect_str = strdup(args[cur_arg+1]); - if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) { - memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.", - args[0], args[1], ptr_arg, args[cur_arg+1], *errmsg); - goto error; - } - } - else if (strcmp(ptr_arg, "rstring") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s %s %s' expects as an argument.", - args[0], args[1], ptr_arg); - goto error; - } - curpx->options2 |= PR_O2_EXP_RSTR; - free(curpx->expect_str); - regex_free(curpx->expect_regex); - curpx->expect_str = strdup(args[cur_arg+1]); - if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) { - memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.", - args[0], args[1], ptr_arg, args[cur_arg + 1], *errmsg); - goto error; - } - } - else { - memprintf(errmsg, "'%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.", - args[0], args[1], ptr_arg); + chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg); + } + + if (!chk) { + memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg); + goto error; + } + ret = (*errmsg != NULL); /* Handle warning */ + + chk->index = index; + if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && + (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) { + /* Use this ruleset if the proxy already has http-check enabled */ + curpx->tcpcheck_rules.list = &rs->rules; + curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS; + if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) { + memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg); + curpx->tcpcheck_rules.list = NULL; goto error; } } else { - memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send', 'send-state' and 'expect'. but got '%s'.", - args[0], args[1]); - goto error; + /* mark this ruleset as unused for now */ + curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS; + LIST_ADDQ(&rs->rules, &chk->list); } - ret = (*errmsg != NULL); /* Handle warning */ + out: return ret; error: + free_tcpcheck(chk, 0); + tcpcheck_ruleset_release(rs); return -1; } @@ -5413,9 +5760,6 @@ int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, str rules->list = NULL; rules->flags = 0; - free(curpx->check_req); - curpx->check_req = NULL; - rules->list = &rs->rules; out: @@ -5476,7 +5820,7 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s "on-error", "%[check.payload(),cut_crlf]", "on-success", "Redis server is ok", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5577,7 +5921,7 @@ int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, "min-recv", "5", "ok-status", "L6OK", "error-status", "L6RSP", "tout-status", "L6TOUT", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5677,7 +6021,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc "error-status", "L7RSP", "on-error", "%[check.payload(),cut_crlf]", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5691,7 +6035,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]", "status-code", "check.payload(0,3)", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5715,7 +6059,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]", "status-code", "check.payload(0,3)", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5848,7 +6192,7 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s "error-status", "L7RSP", "on-error", "%[check.payload(6,0)]", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -5862,7 +6206,7 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s "on-success", "PostgreSQL server is ok", "on-error", "PostgreSQL unknown error", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6075,7 +6419,7 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, s } chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6086,7 +6430,7 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, s if (mysql_req) { chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6163,7 +6507,7 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, st "min-recv", "14", "on-error", "Not LDAPv3 protocol", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6172,7 +6516,7 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, st LIST_ADDQ(&rs->rules, &chk->list); chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6249,7 +6593,7 @@ int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, st LIST_ADDQ(&rs->rules, &chk->list); chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""}, - 1, curpx, &rs->rules, file, line, &errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg); if (!chk) { ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); goto error; @@ -6275,10 +6619,140 @@ int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, st goto out; } + +struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + struct tcpcheck_http_hdr *hdr = NULL; + char *meth = NULL, *uri = NULL, *vsn = NULL; + char *hdrs, *body; + + hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL); + body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL); + if (hdrs == body) + hdrs = NULL; + if (hdrs) { + *hdrs = '\0'; + hdrs +=2; + } + if (body) { + *body = '\0'; + body += 4; + } + if (hdrs || body) { + memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated." + " Please, consider to use 'http-check send' directive instead."); + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_SEND; + chk->send.type = TCPCHK_SEND_HTTP; + chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT; + chk->send.http.meth.meth = HTTP_METH_OPTIONS; + LIST_INIT(&chk->send.http.hdrs); + + /* Copy the method, uri and version */ + if (*args[cur_arg]) { + if (!*args[cur_arg+1]) + uri = args[cur_arg]; + else + meth = args[cur_arg]; + } + if (*args[cur_arg+1]) + uri = args[cur_arg+1]; + if (*args[cur_arg+2]) + vsn = args[cur_arg+2]; + + if (meth) { + chk->send.http.meth.meth = find_http_meth(meth, strlen(meth)); + chk->send.http.meth.str.area = strdup(meth); + chk->send.http.meth.str.data = strlen(meth); + if (!chk->send.http.meth.str.area) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + if (uri) { + chk->send.http.uri = ist2(strdup(uri), strlen(uri)); + if (!chk->send.http.uri.ptr) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + if (vsn) { + chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn)); + if (!chk->send.http.vsn.ptr) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + + /* Copy the header */ + if (hdrs) { + struct http_hdr tmp_hdrs[global.tune.max_http_hdr]; + struct h1m h1m; + int i, ret; + + /* Build and parse the request */ + chunk_printf(&trash, "%s\r\n\r\n", hdrs); + + h1m.flags = H1_MF_HDRS_ONLY; + ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash), + tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]), + &h1m, NULL); + if (ret <= 0) { + memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash)); + goto error; + } + + for (i = 0; tmp_hdrs[i].n.len; i++) { + hdr = calloc(1, sizeof(*hdr)); + if (!hdr) { + memprintf(errmsg, "out of memory"); + goto error; + } + LIST_INIT(&hdr->value); + hdr->name = ist2(strdup(tmp_hdrs[i].n.ptr), tmp_hdrs[i].n.len); + if (!hdr->name.ptr) { + memprintf(errmsg, "out of memory"); + goto error; + } + + tmp_hdrs[i].v.ptr[tmp_hdrs[i].v.len] = '\0'; + if (!parse_logformat_string(tmp_hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg)) + goto error; + LIST_ADDQ(&chk->send.http.hdrs, &hdr->list); + } + } + + /* Copy the body */ + if (body) { + chk->send.http.body = ist2(strdup(body), strlen(body)); + if (!chk->send.http.body.ptr) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + + return chk; + + error: + free_tcpcheck_http_hdr(hdr); + free_tcpcheck(chk, 0); + return NULL; +} + int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, const char *file, int line) { - static const char *http_req = "OPTIONS / HTTP/1.0\r\n"; + struct tcpcheck_ruleset *rs = NULL; + struct tcpcheck_rules *rules = &curpx->tcpcheck_rules; + struct tcpcheck_rule *chk; + char *errmsg = NULL; int err_code = 0; if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL)) @@ -6287,73 +6761,55 @@ int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code)) goto out; - /* use HTTP request to check servers' health */ - free(curpx->check_req); - free(curpx->check_hdrs); - free(curpx->check_body); + chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg); + goto error; + } + if (errmsg) { + ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg); + err_code |= ERR_WARN; + free(errmsg); + errmsg = NULL; + } - curpx->check_req = curpx->check_hdrs = curpx->check_body = NULL; - curpx->check_len = curpx->check_hdrs_len = curpx->check_body_len = 0; + no_request: curpx->options2 &= ~PR_O2_CHK_ANY; - curpx->options2 |= PR_O2_HTTP_CHK; + curpx->options2 |= PR_O2_TCPCHK_CHK; - cur_arg += 2; - if (!*args[cur_arg]) { /* no argument */ - curpx->check_req = strdup(http_req); /* default request */ - curpx->check_len = strlen(http_req); - } - else if (!*args[cur_arg+1]) { /* one argument : URI */ - curpx->check_len = strlen(args[cur_arg]) + strlen("OPTIONS HTTP/1.0\r\n"); - curpx->check_req = malloc(curpx->check_len+1); - curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1, - "OPTIONS %s HTTP/1.0\r\n", args[cur_arg]); - } - else if (!*args[cur_arg+2]) { /* two arguments : METHOD URI */ - curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(" HTTP/1.0\r\n") + 1; - curpx->check_req = malloc(curpx->check_len+1); - curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1, - "%s %s HTTP/1.0\r\n", args[cur_arg], args[cur_arg+1]); - } - else { /* 3 arguments : METHOD URI HTTP_VER */ - char *hdrs = strstr(args[cur_arg+2], "\r\n"); - char *body = strstr(args[cur_arg+2], "\r\n\r\n"); + free_tcpcheck_vars(&rules->preset_vars); + rules->list = NULL; + rules->flags |= TCPCHK_SND_HTTP_FROM_OPT; - if (hdrs || body) { - ha_warning("parsing [%s:%d]: '%s %s' : hiding headers or body at the end of the version string is deprecated." - " Please, consider to use 'http-check send' directive instead.\n", - file, line, args[0], args[1]); - err_code |= ERR_WARN; - } + /* Deduce the ruleset name from the proxy info */ + chunk_printf(&trash, "*http-check-%s_%s-%d", + ((curpx == defpx) ? "defaults" : curpx->id), + curpx->conf.file, curpx->conf.line); - if (hdrs == body) - hdrs = NULL; - if (hdrs) { - *hdrs = '\0'; - hdrs += 2; - } - if (body) { - *body = '\0'; - body += 4; - } - - curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(args[cur_arg+2]) + 4; - curpx->check_req = malloc(curpx->check_len+1); - snprintf(curpx->check_req, curpx->check_len+1, "%s %s %s\r\n", - args[cur_arg], args[cur_arg+1], args[cur_arg+2]); - if (hdrs) { - curpx->check_hdrs_len = strlen(hdrs) + 2; - curpx->check_hdrs = malloc(curpx->check_hdrs_len+1); - snprintf(curpx->check_hdrs, curpx->check_hdrs_len+1, "%s\r\n", hdrs); - } - if (body) { - curpx->check_body_len = strlen(body); - curpx->check_body = strdup(body); + rs = tcpcheck_ruleset_lookup(b_orig(&trash)); + if (rs == NULL) { + rs = tcpcheck_ruleset_create(b_orig(&trash)); + if (rs == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, line); + goto error; } } + + rules->list = &rs->rules; + rules->flags |= TCPCHK_RULES_HTTP_CHK; + if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) { + ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg); + rules->list = NULL; + goto error; + } + out: + free(errmsg); return err_code; error: + tcpcheck_ruleset_release(rs); + free_tcpcheck(chk, 0); err_code |= ERR_ALERT | ERR_FATAL; goto out; } @@ -6363,8 +6819,6 @@ int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx { int err_code = 0; - free(curpx->check_req); - curpx->check_req = NULL; curpx->options2 &= ~PR_O2_CHK_ANY; curpx->options2 |= PR_O2_EXT_CHK; if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code)) @@ -6487,7 +6941,8 @@ static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, LIST_ADDQ(&rs->rules, &chk->list); chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, - 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg); + 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK, + srv->conf.file, srv->conf.line, errmsg); if (!chk) { memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg); goto error; diff --git a/src/haproxy.c b/src/haproxy.c index 982f7b94b..e8b2b3ff8 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -2526,9 +2526,6 @@ void deinit(void) while (p) { free(p->conf.file); free(p->id); - free(p->check_req); - free(p->check_hdrs); - free(p->check_body); free(p->cookie_name); free(p->cookie_domain); free(p->cookie_attrs); diff --git a/src/session.c b/src/session.c index 0c8e344fc..74b029e79 100644 --- a/src/session.c +++ b/src/session.c @@ -216,7 +216,8 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr if (l->proto->drain) l->proto->drain(cfd); if (p->mode == PR_MODE_HTTP || - (p->mode == PR_MODE_HEALTH && (p->options2 & PR_O2_CHK_ANY) == PR_O2_HTTP_CHK)) + (p->mode == PR_MODE_HEALTH && (p->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && + (p->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK)) send(cfd, "HTTP/1.0 200 OK\r\n\r\n", 19, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_MORE); else if (p->mode == PR_MODE_HEALTH) send(cfd, "OK\n", 3, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_MORE);