diff --git a/Makefile b/Makefile index afdd7d738..65ec9adc8 100644 --- a/Makefile +++ b/Makefile @@ -791,7 +791,7 @@ endif OBJS = src/mux_h2.o src/stream.o src/mux_fcgi.o src/cfgparse-listen.o \ src/http_ana.o src/stats.o src/mux_h1.o src/flt_spoe.o src/server.o \ - src/cfgparse.o src/check.o src/backend.o src/log.o src/peers.o \ + src/cfgparse.o src/check.o src/tcpcheck.o src/backend.o src/log.o src/peers.o \ src/cli.o src/haproxy.o src/stick_table.o src/tools.o src/sample.o \ src/proxy.o src/stream_interface.o src/pattern.o src/dns.o \ src/proto_tcp.o src/listener.o src/cfgparse-global.o src/h1.o \ diff --git a/include/haproxy/check-t.h b/include/haproxy/check-t.h index 832a3f552..fb2f0a1f2 100644 --- a/include/haproxy/check-t.h +++ b/include/haproxy/check-t.h @@ -136,98 +136,8 @@ enum { HANA_OBS_SIZE }; -/* options for tcp-check connect */ -#define TCPCHK_OPT_NONE 0x0000 /* no options specified, default */ -#define TCPCHK_OPT_SEND_PROXY 0x0001 /* send proxy-protocol string */ -#define TCPCHK_OPT_SSL 0x0002 /* SSL connection */ -#define TCPCHK_OPT_LINGER 0x0004 /* Do not RST connection, let it linger */ -#define TCPCHK_OPT_DEFAULT_CONNECT 0x0008 /* Do a connect using server params */ -#define TCPCHK_OPT_IMPLICIT 0x0010 /* Implicit connect */ -#define TCPCHK_OPT_SOCKS4 0x0020 /* check the connection via socks4 proxy */ - -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_HTTP, /* Send an HTTP request */ -}; - -/* flags for tcp-check send */ -#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 */ - -enum tcpcheck_eval_ret { - TCPCHK_EVAL_WAIT = 0, - TCPCHK_EVAL_STOP, - TCPCHK_EVAL_CONTINUE, -}; - -enum tcpcheck_expect_type { - TCPCHK_EXPECT_UNDEF = 0, /* Match is not used. */ - TCPCHK_EXPECT_STRING, /* Matches a string. */ - TCPCHK_EXPECT_STRING_REGEX, /* Matches a regular pattern. */ - TCPCHK_EXPECT_STRING_LF, /* Matches a log-format string. */ - TCPCHK_EXPECT_BINARY, /* Matches a binary sequence on a hex-encoded text. */ - TCPCHK_EXPECT_BINARY_REGEX, /* Matches a regular pattern on a hex-encoded text. */ - TCPCHK_EXPECT_BINARY_LF, /* Matches a log-format binary sequence on a hex-encoded text. */ - TCPCHK_EXPECT_CUSTOM, /* Execute a custom function. */ - TCPCHK_EXPECT_HTTP_STATUS, /* Matches a list of codes on the HTTP status */ - TCPCHK_EXPECT_HTTP_STATUS_REGEX, /* Matches a regular pattern on the HTTP status */ - TCPCHK_EXPECT_HTTP_HEADER, /* Matches on HTTP headers */ - TCPCHK_EXPECT_HTTP_BODY, /* Matches a string oa the HTTP payload */ - TCPCHK_EXPECT_HTTP_BODY_REGEX, /* Matches a regular pattern on a HTTP payload */ - TCPCHK_EXPECT_HTTP_BODY_LF, /* Matches a log-format string on the HTTP payload */ -}; - -/* tcp-check expect flags */ -#define TCPCHK_EXPT_FL_INV 0x0001 /* Matching is inversed */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_STR 0x0002 /* Exact match on the HTTP header name */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_BEG 0x0004 /* Prefix match on the HTTP header name */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_END 0x0008 /* Suffix match on the HTTP header name */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_SUB 0x0010 /* Substring match on the HTTP header name */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_REG 0x0020 /* Regex match on the HTTP header name */ -#define TCPCHK_EXPT_FL_HTTP_HNAME_FMT 0x0040 /* The HTTP header name is a log-format string */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_NONE 0x0080 /* No match on the HTTP header value */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_STR 0x0100 /* Exact match on the HTTP header value */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_BEG 0x0200 /* Prefix match on the HTTP header value */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_END 0x0400 /* Suffix match on the HTTP header value */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_SUB 0x0800 /* Substring match on the HTTP header value */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_REG 0x1000 /* Regex match on the HTTP header value*/ -#define TCPCHK_EXPT_FL_HTTP_HVAL_FMT 0x2000 /* The HTTP header value is a log-format string */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_FULL 0x4000 /* Match the full header value ( no stop on commas ) */ - -#define TCPCHK_EXPT_FL_HTTP_HNAME_TYPE 0x003E /* Mask to get matching method on header name */ -#define TCPCHK_EXPT_FL_HTTP_HVAL_TYPE 0x1F00 /* Mask to get matching method on header value */ - -/* possible actions for tcpcheck_rule->action */ -enum tcpcheck_rule_type { - TCPCHK_ACT_SEND = 0, /* send action, regular string format */ - TCPCHK_ACT_EXPECT, /* expect action, either regular or binary string */ - TCPCHK_ACT_CONNECT, /* connect action, to probe a new port */ - TCPCHK_ACT_COMMENT, /* no action, simply a comment used for logs */ - TCPCHK_ACT_ACTION_KW, /* custom registered action_kw 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 -#define TCPCHK_RULES_AGENT_CHK 0x00000080 -#define TCPCHK_RULES_SPOP_CHK 0x00000090 -/* Unused 0x000000A0..0x00000FF0 (reserverd for futur proto) */ -#define TCPCHK_RULES_TCP_CHK 0x00000FF0 -#define TCPCHK_RULES_PROTO_CHK 0x00000FF0 /* Mask to cover protocol check */ +struct tcpcheck_rule; +struct tcpcheck_rules; struct check { enum obj_type obj_type; /* object type == OBJ_TYPE_CHECK */ @@ -267,121 +177,4 @@ struct check { int via_socks4; /* check the connection via socks4 proxy */ }; -struct tcpcheck_connect { - char *sni; /* server name to use for SSL connections */ - char *alpn; /* ALPN to use for the SSL connection */ - int alpn_len; /* ALPN string length */ - const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */ - uint16_t options; /* options when setting up a new connection */ - uint16_t port; /* port to connect to */ - struct sample_expr *port_expr; /* sample expr to determine the port, may be NULL */ - struct sockaddr_storage addr; /* the address to the connect */ -}; - -struct tcpcheck_http_hdr { - struct ist name; /* the header name */ - struct list value; /* the log-format string value */ - struct list list; /* header chained list */ -}; - -struct tcpcheck_codes { - unsigned int (*codes)[2]; /* an array of roange of codes: [0]=min [1]=max */ - size_t num; /* number of entry in the array */ -}; - -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 */ - }; -}; - -struct tcpcheck_expect { - enum tcpcheck_expect_type type; /* Type of pattern used for matching. */ - unsigned int flags; /* TCPCHK_EXPT_FL_* */ - union { - struct ist data; /* Matching a literal string / binary anywhere in the response. */ - struct my_regex *regex; /* Matching a regex pattern. */ - struct tcpcheck_codes codes; /* Matching a list of codes */ - struct list fmt; /* Matching a log-format string / binary */ - struct { - union { - struct ist name; - struct list name_fmt; - struct my_regex *name_re; - }; - union { - struct ist value; - struct list value_fmt; - struct my_regex *value_re; - }; - } hdr; /* Matching a header pattern */ - - - /* custom function to eval epxect rule */ - enum tcpcheck_eval_ret (*custom)(struct check *, struct tcpcheck_rule *, int); - }; - 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) */ - 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 */ -}; - -struct tcpcheck_action_kw { - struct act_rule *rule; -}; - -struct tcpcheck_rule { - struct list list; /* list linked to from the proxy */ - enum tcpcheck_rule_type action; /* type of the rule. */ - int index; /* Index within the list. Starts at 0. */ - char *comment; /* comment to be used in the logs and on the stats socket */ - union { - struct tcpcheck_connect connect; /* Connect rule. */ - struct tcpcheck_send send; /* Send rule. */ - struct tcpcheck_expect expect; /* Expected pattern. */ - struct tcpcheck_action_kw action_kw; /* Custom action. */ - }; -}; - -/* A list of tcp-check vars, to be registered before executing a ruleset */ -struct tcpcheck_var { - struct ist name; /* the variable name with the scope */ - struct sample_data data; /* the data associated to the variable */ - struct list list; /* element to chain tcp-check vars */ -}; - -/* a list of tcp-check rules */ -struct tcpcheck_rules { - unsigned int flags; /* flags applied to the rules */ - struct list *list; /* the list of tcpcheck_rules */ - struct list preset_vars; /* The list of variable to preset before executing the ruleset */ -}; - -/* A list of tcp-check rules with a name */ -struct tcpcheck_ruleset { - struct list rules; /* the list of tcpcheck_rule */ - struct ebpt_node node; /* node in the shared tree */ -}; - - #endif /* _HAPROXY_CHECKS_T_H */ diff --git a/include/haproxy/check.h b/include/haproxy/check.h index 637909cb5..83edf4f4f 100644 --- a/include/haproxy/check.h +++ b/include/haproxy/check.h @@ -22,29 +22,24 @@ #ifndef _HAPROXY_CHECKS_H #define _HAPROXY_CHECKS_H -#include #include #include #include #include -extern struct action_kw_list tcp_check_keywords; -extern struct pool_head *pool_head_tcpcheck_rule; +extern struct data_cb check_conn_cb; +extern struct proxy checks_fe; const char *get_check_status_description(short check_status); const char *get_check_status_info(short check_status); +int httpchk_build_status_header(struct server *s, struct buffer *buf); void __health_adjust(struct server *s, short status); +void set_server_check_status(struct check *check, short status, const char *desc); +void chk_report_conn_err(struct check *check, int errno_bck, int expired); struct task *process_chk(struct task *t, void *context, unsigned short state); const char *init_check(struct check *check, int type); void free_check(struct check *check); -void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool); - -void deinit_proxy_tcpcheck(struct proxy *px); -int dup_tcpcheck_vars(struct list *dst, struct list *src); -void free_tcpcheck_vars(struct list *vars); -int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str); -int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs); /* Declared here, but the definitions are in flt_spoe.c */ int spoe_prepare_healthcheck_request(char **req, int *len); @@ -89,11 +84,6 @@ static inline void health_adjust(struct server *s, short status) HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); } -static inline void tcp_check_keywords_register(struct action_kw_list *kw_list) -{ - LIST_ADDQ(&tcp_check_keywords.list, &kw_list->list); -} - #endif /* _HAPROXY_CHECKS_H */ /* diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h index 24cad0f80..435d10942 100644 --- a/include/haproxy/connection.h +++ b/include/haproxy/connection.h @@ -33,6 +33,7 @@ #include #include #include +#include extern struct pool_head *pool_head_connection; diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index f7b099786..507ee7052 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include diff --git a/include/haproxy/tcpcheck-t.h b/include/haproxy/tcpcheck-t.h new file mode 100644 index 000000000..b65b2517a --- /dev/null +++ b/include/haproxy/tcpcheck-t.h @@ -0,0 +1,242 @@ +/* + * include/haproxy/tcpcheck-t.h + * TCP check definitions, enums, macros and bitfields. + * + * Copyright 2000-2009,2020 Willy Tarreau + * Copyright 2007-2010 Krzysztof Piotr Oledzki + * Copyright 2013 Baptiste Assmann + * Copyright 2020 Gaetan Rivet + * Copyright 2020 Christopher Faulet + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#ifndef _HAPROXY_TCPCHECK_T_H +#define _HAPROXY_TCPCHECK_T_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* options for tcp-check connect */ +#define TCPCHK_OPT_NONE 0x0000 /* no options specified, default */ +#define TCPCHK_OPT_SEND_PROXY 0x0001 /* send proxy-protocol string */ +#define TCPCHK_OPT_SSL 0x0002 /* SSL connection */ +#define TCPCHK_OPT_LINGER 0x0004 /* Do not RST connection, let it linger */ +#define TCPCHK_OPT_DEFAULT_CONNECT 0x0008 /* Do a connect using server params */ +#define TCPCHK_OPT_IMPLICIT 0x0010 /* Implicit connect */ +#define TCPCHK_OPT_SOCKS4 0x0020 /* check the connection via socks4 proxy */ + +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_HTTP, /* Send an HTTP request */ +}; + +/* flags for tcp-check send */ +#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 */ + +enum tcpcheck_eval_ret { + TCPCHK_EVAL_WAIT = 0, + TCPCHK_EVAL_STOP, + TCPCHK_EVAL_CONTINUE, +}; + +enum tcpcheck_expect_type { + TCPCHK_EXPECT_UNDEF = 0, /* Match is not used. */ + TCPCHK_EXPECT_STRING, /* Matches a string. */ + TCPCHK_EXPECT_STRING_REGEX, /* Matches a regular pattern. */ + TCPCHK_EXPECT_STRING_LF, /* Matches a log-format string. */ + TCPCHK_EXPECT_BINARY, /* Matches a binary sequence on a hex-encoded text. */ + TCPCHK_EXPECT_BINARY_REGEX, /* Matches a regular pattern on a hex-encoded text. */ + TCPCHK_EXPECT_BINARY_LF, /* Matches a log-format binary sequence on a hex-encoded text. */ + TCPCHK_EXPECT_CUSTOM, /* Execute a custom function. */ + TCPCHK_EXPECT_HTTP_STATUS, /* Matches a list of codes on the HTTP status */ + TCPCHK_EXPECT_HTTP_STATUS_REGEX, /* Matches a regular pattern on the HTTP status */ + TCPCHK_EXPECT_HTTP_HEADER, /* Matches on HTTP headers */ + TCPCHK_EXPECT_HTTP_BODY, /* Matches a string oa the HTTP payload */ + TCPCHK_EXPECT_HTTP_BODY_REGEX, /* Matches a regular pattern on a HTTP payload */ + TCPCHK_EXPECT_HTTP_BODY_LF, /* Matches a log-format string on the HTTP payload */ +}; + +/* tcp-check expect flags */ +#define TCPCHK_EXPT_FL_INV 0x0001 /* Matching is inversed */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_STR 0x0002 /* Exact match on the HTTP header name */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_BEG 0x0004 /* Prefix match on the HTTP header name */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_END 0x0008 /* Suffix match on the HTTP header name */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_SUB 0x0010 /* Substring match on the HTTP header name */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_REG 0x0020 /* Regex match on the HTTP header name */ +#define TCPCHK_EXPT_FL_HTTP_HNAME_FMT 0x0040 /* The HTTP header name is a log-format string */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_NONE 0x0080 /* No match on the HTTP header value */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_STR 0x0100 /* Exact match on the HTTP header value */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_BEG 0x0200 /* Prefix match on the HTTP header value */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_END 0x0400 /* Suffix match on the HTTP header value */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_SUB 0x0800 /* Substring match on the HTTP header value */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_REG 0x1000 /* Regex match on the HTTP header value*/ +#define TCPCHK_EXPT_FL_HTTP_HVAL_FMT 0x2000 /* The HTTP header value is a log-format string */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_FULL 0x4000 /* Match the full header value ( no stop on commas ) */ + +#define TCPCHK_EXPT_FL_HTTP_HNAME_TYPE 0x003E /* Mask to get matching method on header name */ +#define TCPCHK_EXPT_FL_HTTP_HVAL_TYPE 0x1F00 /* Mask to get matching method on header value */ + +/* possible actions for tcpcheck_rule->action */ +enum tcpcheck_rule_type { + TCPCHK_ACT_SEND = 0, /* send action, regular string format */ + TCPCHK_ACT_EXPECT, /* expect action, either regular or binary string */ + TCPCHK_ACT_CONNECT, /* connect action, to probe a new port */ + TCPCHK_ACT_COMMENT, /* no action, simply a comment used for logs */ + TCPCHK_ACT_ACTION_KW, /* custom registered action_kw 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 +#define TCPCHK_RULES_AGENT_CHK 0x00000080 +#define TCPCHK_RULES_SPOP_CHK 0x00000090 +/* Unused 0x000000A0..0x00000FF0 (reserverd for futur proto) */ +#define TCPCHK_RULES_TCP_CHK 0x00000FF0 +#define TCPCHK_RULES_PROTO_CHK 0x00000FF0 /* Mask to cover protocol check */ + +struct check; +struct tcpcheck_connect { + char *sni; /* server name to use for SSL connections */ + char *alpn; /* ALPN to use for the SSL connection */ + int alpn_len; /* ALPN string length */ + const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */ + uint16_t options; /* options when setting up a new connection */ + uint16_t port; /* port to connect to */ + struct sample_expr *port_expr; /* sample expr to determine the port, may be NULL */ + struct sockaddr_storage addr; /* the address to the connect */ +}; + +struct tcpcheck_http_hdr { + struct ist name; /* the header name */ + struct list value; /* the log-format string value */ + struct list list; /* header chained list */ +}; + +struct tcpcheck_codes { + unsigned int (*codes)[2]; /* an array of roange of codes: [0]=min [1]=max */ + size_t num; /* number of entry in the array */ +}; + +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 */ + }; +}; + +struct tcpcheck_expect { + enum tcpcheck_expect_type type; /* Type of pattern used for matching. */ + unsigned int flags; /* TCPCHK_EXPT_FL_* */ + union { + struct ist data; /* Matching a literal string / binary anywhere in the response. */ + struct my_regex *regex; /* Matching a regex pattern. */ + struct tcpcheck_codes codes; /* Matching a list of codes */ + struct list fmt; /* Matching a log-format string / binary */ + struct { + union { + struct ist name; + struct list name_fmt; + struct my_regex *name_re; + }; + union { + struct ist value; + struct list value_fmt; + struct my_regex *value_re; + }; + } hdr; /* Matching a header pattern */ + + + /* custom function to eval epxect rule */ + enum tcpcheck_eval_ret (*custom)(struct check *, struct tcpcheck_rule *, int); + }; + 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) */ + 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 */ +}; + +struct tcpcheck_action_kw { + struct act_rule *rule; +}; + +struct tcpcheck_rule { + struct list list; /* list linked to from the proxy */ + enum tcpcheck_rule_type action; /* type of the rule. */ + int index; /* Index within the list. Starts at 0. */ + char *comment; /* comment to be used in the logs and on the stats socket */ + union { + struct tcpcheck_connect connect; /* Connect rule. */ + struct tcpcheck_send send; /* Send rule. */ + struct tcpcheck_expect expect; /* Expected pattern. */ + struct tcpcheck_action_kw action_kw; /* Custom action. */ + }; +}; + +/* A list of tcp-check vars, to be registered before executing a ruleset */ +struct tcpcheck_var { + struct ist name; /* the variable name with the scope */ + struct sample_data data; /* the data associated to the variable */ + struct list list; /* element to chain tcp-check vars */ +}; + +/* a list of tcp-check rules */ +struct tcpcheck_rules { + unsigned int flags; /* flags applied to the rules */ + struct list *list; /* the list of tcpcheck_rules */ + struct list preset_vars; /* The list of variable to preset before executing the ruleset */ +}; + +/* A list of tcp-check rules with a name */ +struct tcpcheck_ruleset { + struct list rules; /* the list of tcpcheck_rule */ + struct ebpt_node node; /* node in the shared tree */ +}; + + +#endif /* _HAPROXY_CHECKS_T_H */ diff --git a/include/haproxy/tcpcheck.h b/include/haproxy/tcpcheck.h new file mode 100644 index 000000000..4583dbb1a --- /dev/null +++ b/include/haproxy/tcpcheck.h @@ -0,0 +1,112 @@ +/* + * include/haproxy/tcpcheck.h + * Functions prototypes for the TCP checks. + * + * Copyright 2000-2009,2020 Willy Tarreau + * Copyright 2007-2010 Krzysztof Piotr Oledzki + * Copyright 2013 Baptiste Assmann + * Copyright 2020 Gaetan Rivet + * Copyright 2020 Christopher Faulet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_TCPCHECK_H +#define _HAPROXY_TCPCHECK_H + +#include +#include +#include +#include +#include +#include + +extern struct action_kw_list tcp_check_keywords; +extern struct pool_head *pool_head_tcpcheck_rule; + +int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule); +struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules); + +struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name); +struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name); +void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs); + +void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool); +void deinit_proxy_tcpcheck(struct proxy *px); + +struct tcpcheck_var *create_tcpcheck_var(const struct ist name); +void free_tcpcheck_var(struct tcpcheck_var *var); +int dup_tcpcheck_vars(struct list *dst, struct list *src); +void free_tcpcheck_vars(struct list *vars); + +int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str); +int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs); +int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg); + +void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr); + +enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule); +enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule); +enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule); +enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read); +enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule); +int tcpcheck_main(struct check *check); +struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px, + struct list *rules, struct action_kw *kw, + const char *file, int line, char **errmsg); +struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg); +struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg); +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 *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg); +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); + + + +/* Return the struct action_kw associated to a keyword */ +static inline struct action_kw *action_kw_tcp_check_lookup(const char *kw) +{ + return action_lookup(&tcp_check_keywords.list, kw); +} + +static inline void action_kw_tcp_check_build_list(struct buffer *chk) +{ + action_build_list(&tcp_check_keywords.list, chk); +} + +static inline void tcp_check_keywords_register(struct action_kw_list *kw_list) +{ + LIST_ADDQ(&tcp_check_keywords.list, &kw_list->list); +} + +#endif /* _HAPROXY_TCPCHECK_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 0544df448..4167f156a 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/src/check.c b/src/check.c index fa608c9df..b508f8579 100644 --- a/src/check.c +++ b/src/check.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -68,22 +69,15 @@ #include #include -static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *); - static int wake_srv_chk(struct conn_stream *cs); struct data_cb check_conn_cb = { .wake = wake_srv_chk, .name = "CHCK", }; -/* Global tree to share all tcp-checks */ -struct eb_root shared_tcpchecks = EB_ROOT; - - -DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule)); /* Dummy frontend used to create all checks sessions. */ -static struct proxy checks_fe; +struct proxy checks_fe; /**************************************************************************/ /************************ Handle check results ****************************/ @@ -214,7 +208,7 @@ const char *get_analyze_status(short analyze_status) { * Shows information in logs about failed health check if server is UP or * succeeded health checks if server is DOWN. */ -static void set_server_check_status(struct check *check, short status, const char *desc) +void set_server_check_status(struct check *check, short status, const char *desc) { struct server *s = check->server; short prev_status = check->status; @@ -540,7 +534,7 @@ static int retrieve_errno_from_socket(struct connection *conn) * All situations where at least one of or CO_FL_ERROR are set * produce a status. */ -static void chk_report_conn_err(struct check *check, int errno_bck, int expired) +void chk_report_conn_err(struct check *check, int errno_bck, int expired) { struct conn_stream *cs = check->cs; struct connection *conn = cs_conn(cs); @@ -707,475 +701,8 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired) } -/**************************************************************************/ -/*************** Init/deinit tcp-check rules and ruleset ******************/ -/**************************************************************************/ -/* Releases memory allocated for a log-format string */ -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); - } -} - -/* Releases memory allocated for an HTTP header used in a tcp-check send rule */ -static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr) -{ - if (!hdr) - return; - - free_tcpcheck_fmt(&hdr->value); - istfree(&hdr->name); - free(hdr); -} - -/* Releases memory allocated for an HTTP header list used in a tcp-check send - * rule - */ -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); - } -} - -/* Releases memory allocated for a tcp-check. If in_pool is set, it means the - * tcp-check was allocated using a memory pool (it is used to instantiate email - * alerts). - */ -void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) -{ - if (!rule) - return; - - free(rule->comment); - switch (rule->action) { - case TCPCHK_ACT_SEND: - switch (rule->send.type) { - case TCPCHK_SEND_STRING: - case TCPCHK_SEND_BINARY: - istfree(&rule->send.data); - break; - case TCPCHK_SEND_STRING_LF: - case TCPCHK_SEND_BINARY_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)) - istfree(&rule->send.http.uri); - else - free_tcpcheck_fmt(&rule->send.http.uri_fmt); - istfree(&rule->send.http.vsn); - free_tcpcheck_http_hdrs(&rule->send.http.hdrs); - if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT)) - istfree(&rule->send.http.body); - else - free_tcpcheck_fmt(&rule->send.http.body_fmt); - break; - case TCPCHK_SEND_UNDEF: - break; - } - break; - case TCPCHK_ACT_EXPECT: - 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_HTTP_STATUS: - free(rule->expect.codes.codes); - break; - case TCPCHK_EXPECT_STRING: - case TCPCHK_EXPECT_BINARY: - case TCPCHK_EXPECT_HTTP_BODY: - istfree(&rule->expect.data); - break; - case TCPCHK_EXPECT_STRING_REGEX: - case TCPCHK_EXPECT_BINARY_REGEX: - case TCPCHK_EXPECT_HTTP_STATUS_REGEX: - case TCPCHK_EXPECT_HTTP_BODY_REGEX: - regex_free(rule->expect.regex); - break; - case TCPCHK_EXPECT_STRING_LF: - case TCPCHK_EXPECT_BINARY_LF: - case TCPCHK_EXPECT_HTTP_BODY_LF: - free_tcpcheck_fmt(&rule->expect.fmt); - break; - case TCPCHK_EXPECT_HTTP_HEADER: - if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) - regex_free(rule->expect.hdr.name_re); - else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) - free_tcpcheck_fmt(&rule->expect.hdr.name_fmt); - else - istfree(&rule->expect.hdr.name); - - if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) - regex_free(rule->expect.hdr.value_re); - else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) - free_tcpcheck_fmt(&rule->expect.hdr.value_fmt); - else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE)) - istfree(&rule->expect.hdr.value); - break; - case TCPCHK_EXPECT_CUSTOM: - case TCPCHK_EXPECT_UNDEF: - break; - } - break; - case TCPCHK_ACT_CONNECT: - free(rule->connect.sni); - free(rule->connect.alpn); - release_sample_expr(rule->connect.port_expr); - break; - case TCPCHK_ACT_COMMENT: - break; - case TCPCHK_ACT_ACTION_KW: - free(rule->action_kw.rule); - break; - } - - if (in_pool) - pool_free(pool_head_tcpcheck_rule, rule); - else - free(rule); -} - -/* Creates a tcp-check variable used in preset variables before executing a - * tcp-check ruleset. - */ -static struct tcpcheck_var *create_tcpcheck_var(const struct ist name) -{ - struct tcpcheck_var *var = NULL; - - var = calloc(1, sizeof(*var)); - if (var == NULL) - return NULL; - - var->name = istdup(name); - if (!isttest(var->name)) { - free(var); - return NULL; - } - - LIST_INIT(&var->list); - return var; -} - -/* Releases memory allocated for a preset tcp-check variable */ -static void free_tcpcheck_var(struct tcpcheck_var *var) -{ - if (!var) - return; - - istfree(&var->name); - if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) - free(var->data.u.str.area); - else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) - free(var->data.u.meth.str.area); - free(var); -} - -/* Releases a list of preset tcp-check variables */ -void free_tcpcheck_vars(struct list *vars) -{ - struct tcpcheck_var *var, *back; - - list_for_each_entry_safe(var, back, vars, list) { - LIST_DEL(&var->list); - free_tcpcheck_var(var); - } -} - -/* Duplicate a list of preset tcp-check variables */ -int dup_tcpcheck_vars(struct list *dst, struct list *src) -{ - struct tcpcheck_var *var, *new = NULL; - - list_for_each_entry(var, src, list) { - new = create_tcpcheck_var(var->name); - if (!new) - goto error; - new->data.type = var->data.type; - if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) { - if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL) - goto error; - if (var->data.type == SMP_T_STR) - new->data.u.str.area[new->data.u.str.data] = 0; - } - else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) { - if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL) - goto error; - new->data.u.str.area[new->data.u.str.data] = 0; - new->data.u.meth.meth = var->data.u.meth.meth; - } - else - new->data.u = var->data.u; - LIST_ADDQ(dst, &new->list); - } - return 1; - - error: - free(new); - return 0; -} - -/* Looks for a shared tcp-check ruleset given its name. */ -static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name) -{ - struct tcpcheck_ruleset *rs; - struct ebpt_node *node; - - node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name)); - if (node) { - rs = container_of(node, typeof(*rs), node); - return rs; - } - return NULL; -} - -/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks - * tree. - */ -static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name) -{ - struct tcpcheck_ruleset *rs; - - rs = calloc(1, sizeof(*rs)); - if (rs == NULL) - return NULL; - - rs->node.key = strdup(name); - if (rs->node.key == NULL) { - free(rs); - return NULL; - } - - LIST_INIT(&rs->rules); - ebis_insert(&shared_tcpchecks, &rs->node); - return rs; -} - -/* Releases memory allocated by a tcp-check ruleset. */ -static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs) -{ - struct tcpcheck_rule *r, *rb; - if (!rs) - return; - - ebpt_delete(&rs->node); - free(rs->node.key); - list_for_each_entry_safe(r, rb, &rs->rules, list) { - LIST_DEL(&r->list); - free_tcpcheck(r, 0); - } - free(rs); -} - - -/**************************************************************************/ -/**************** Everything about tcp-checks execution *******************/ -/**************************************************************************/ -/* Returns the id of a step in a tcp-check ruleset */ -static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule) -{ - if (!rule) - rule = check->current_step; - - /* no last started step => first step */ - if (!rule) - return 1; - - /* last step is the first implicit connect */ - if (rule->index == 0 && - rule->action == TCPCHK_ACT_CONNECT && - (rule->connect.options & TCPCHK_OPT_IMPLICIT)) - return 0; - - return rule->index + 1; -} - -/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list or - * NULL if none was found. - */ -static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules) -{ - struct tcpcheck_rule *r; - - list_for_each_entry(r, rules->list, list) { - if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) - return r; - } - return NULL; -} - -/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list or - * NULL if none was found. - */ -static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules) -{ - struct tcpcheck_rule *r; - - list_for_each_entry_rev(r, rules->list, list) { - if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) - return r; - } - return NULL; -} - -/* Returns the non COMMENT/ACTION_KW tcp-check rule from list following - * or NULL if non was found. If is NULL, it relies on - * get_first_tcpcheck_rule(). - */ -static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start) -{ - struct tcpcheck_rule *r; - - if (!start) - return get_first_tcpcheck_rule(rules); - - r = LIST_NEXT(&start->list, typeof(r), list); - list_for_each_entry_from(r, rules->list, list) { - if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) - return r; - } - return NULL; -} - - -/* Creates info message when a tcp-check healthcheck fails on an expect rule */ -static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule, - int match, struct ist info) -{ - struct sample *smp; - - /* Follows these step to produce the info message: - * 1. if info field is already provided, copy it - * 2. if the expect rule provides an onerror log-format string, - * use it to produce the message - * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing - * 4. Otherwise produce the generic tcp-check info message - */ - if (istlen(info)) { - chunk_strncat(msg, istptr(info), istlen(info)); - goto comment; - } - else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) { - msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt); - goto comment; - } - - if (check->type == PR_O2_TCPCHK_CHK && - (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK) - goto comment; - - chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content")); - switch (rule->expect.type) { - case TCPCHK_EXPECT_HTTP_STATUS: - chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_STRING: - case TCPCHK_EXPECT_HTTP_BODY: - chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data), - tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_BINARY: - chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_STRING_REGEX: - case TCPCHK_EXPECT_HTTP_STATUS_REGEX: - case TCPCHK_EXPECT_HTTP_BODY_REGEX: - chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_BINARY_REGEX: - chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_STRING_LF: - case TCPCHK_EXPECT_HTTP_BODY_LF: - chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_BINARY_LF: - chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_CUSTOM: - chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule)); - break; - case TCPCHK_EXPECT_HTTP_HEADER: - chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule)); - case TCPCHK_EXPECT_UNDEF: - /* Should never happen. */ - return; - } - - comment: - /* If the failing expect rule provides a comment, it is concatenated to - * the info message. - */ - if (rule->comment) { - chunk_strcat(msg, " comment: "); - chunk_strcat(msg, rule->comment); - } - - /* Finally, the check status code is set if the failing expect rule - * defines a status expression. - */ - if (rule->expect.status_expr) { - smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL, - rule->expect.status_expr, SMP_T_STR); - - if (smp && sample_casts[smp->data.type][SMP_T_SINT] && - sample_casts[smp->data.type][SMP_T_SINT](smp)) - check->code = smp->data.u.sint; - } - - *(b_tail(msg)) = '\0'; -} - -/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */ -static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule, - struct ist info) -{ - struct sample *smp; - - /* Follows these step to produce the info message: - * 1. if info field is already provided, copy it - * 2. if the expect rule provides an onsucces log-format string, - * use it to produce the message - * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing - * 4. Otherwise produce the generic tcp-check info message - */ - if (istlen(info)) - chunk_strncat(msg, istptr(info), istlen(info)); - if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt)) - msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), - &rule->expect.onsuccess_fmt); - else if (check->type == PR_O2_TCPCHK_CHK && - (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) - chunk_strcat(msg, "(tcp-check)"); - - /* Finally, the check status code is set if the expect rule defines a - * status expression. - */ - if (rule->expect.status_expr) { - smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL, - rule->expect.status_expr, SMP_T_STR); - - if (smp && sample_casts[smp->data.type][SMP_T_SINT] && - sample_casts[smp->data.type][SMP_T_SINT](smp)) - check->code = smp->data.u.sint; - } - - *(b_tail(msg)) = '\0'; -} - /* Builds the server state header used by HTTP health-checks */ -static int httpchk_build_status_header(struct server *s, struct buffer *buf) +int httpchk_build_status_header(struct server *s, struct buffer *buf) { int sv_state; int ratio; @@ -1231,1606 +758,6 @@ static int httpchk_build_status_header(struct server *s, struct buffer *buf) return b_data(buf); } -/* Internal functions to parse and validate a MySQL packet in the context of an - * expect rule. It start to parse the input buffer at the offset . If - * is set, no more data are expected. - */ -static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule, - unsigned int offset, int last_read) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - enum healthcheck_status status; - struct buffer *msg = NULL; - struct ist desc = IST_NULL; - unsigned int err = 0, plen = 0; - - - /* 3 Bytes for the packet length and 1 byte for the sequence id */ - if (b_data(&check->bi) < offset+4) { - if (!last_read) - goto wait_more_data; - - /* invalid length or truncated response */ - status = HCHK_STATUS_L7RSP; - goto error; - } - - plen = ((unsigned char) *b_peek(&check->bi, offset)) + - (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) + - (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16); - - if (b_data(&check->bi) < offset+plen+4) { - if (!last_read) - goto wait_more_data; - - /* invalid length or truncated response */ - status = HCHK_STATUS_L7RSP; - goto error; - } - - if (*b_peek(&check->bi, offset+4) == '\xff') { - /* MySQL Error packet always begin with field_count = 0xff */ - status = HCHK_STATUS_L7STS; - err = ((unsigned char) *b_peek(&check->bi, offset+5)) + - (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8); - desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7); - goto error; - } - - if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) { - /* Not the last rule, continue */ - goto out; - } - - /* We set the MySQL Version in description for information purpose - * FIXME : it can be cool to use MySQL Version for other purpose, - * like mark as down old MySQL server. - */ - status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); - set_server_check_status(check, status, b_peek(&check->bi, 5)); - - out: - free_trash_chunk(msg); - return ret; - - error: - ret = TCPCHK_EVAL_STOP; - check->code = err; - msg = alloc_trash_chunk(); - if (msg) - tcpcheck_expect_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; -} - -/* Custom tcp-check expect function to parse and validate the MySQL initial - * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data, - * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an - * error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - return tcpcheck_mysql_expect_packet(check, rule, 0, last_read); -} - -/* Custom tcp-check expect function to parse and validate the MySQL OK packet - * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more - * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if - * an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - unsigned int hslen = 0; - - hslen = 4 + ((unsigned char) *b_head(&check->bi)) + - (((unsigned char) *(b_peek(&check->bi, 1))) << 8) + - (((unsigned char) *(b_peek(&check->bi, 2))) << 16); - - return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read); -} - -/* Custom tcp-check expect function to parse and validate the LDAP bind response - * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data, - * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an - * error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - enum healthcheck_status status; - struct buffer *msg = NULL; - struct ist desc = IST_NULL; - unsigned short msglen = 0; - - /* Check if the server speaks LDAP (ASN.1/BER) - * http://en.wikipedia.org/wiki/Basic_Encoding_Rules - * http://tools.ietf.org/html/rfc4511 - */ - /* size of LDAPMessage */ - msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0; - - /* http://tools.ietf.org/html/rfc4511#section-4.2.2 - * messageID: 0x02 0x01 0x01: INTEGER 1 - * protocolOp: 0x61: bindResponse - */ - if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) { - status = HCHK_STATUS_L7RSP; - desc = ist("Not LDAPv3 protocol"); - goto error; - } - - /* size of bindResponse */ - msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0; - - /* http://tools.ietf.org/html/rfc4511#section-4.1.9 - * ldapResult: 0x0a 0x01: ENUMERATION - */ - if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) { - status = HCHK_STATUS_L7RSP; - desc = ist("Not LDAPv3 protocol"); - goto error; - } - - /* http://tools.ietf.org/html/rfc4511#section-4.1.9 - * resultCode - */ - check->code = *(b_head(&check->bi) + msglen + 9); - if (check->code) { - status = HCHK_STATUS_L7STS; - desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9"); - goto error; - } - - status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); - set_server_check_status(check, status, "Success"); - - out: - free_trash_chunk(msg); - return ret; - - error: - ret = TCPCHK_EVAL_STOP; - msg = alloc_trash_chunk(); - if (msg) - tcpcheck_expect_onerror_message(msg, check, rule, 0, desc); - set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); - goto out; -} - -/* Custom tcp-check expect function to parse and validate the SPOP hello agent - * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE - * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - enum healthcheck_status status; - struct buffer *msg = NULL; - struct ist desc = IST_NULL; - unsigned int framesz; - - - memcpy(&framesz, b_head(&check->bi), 4); - framesz = ntohl(framesz); - - if (!last_read && b_data(&check->bi) < (4+framesz)) - goto wait_more_data; - - memset(b_orig(&trash), 0, b_size(&trash)); - if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) { - status = HCHK_STATUS_L7RSP; - desc = ist2(b_orig(&trash), strlen(b_orig(&trash))); - goto error; - } - - status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); - set_server_check_status(check, status, "SPOA server is ok"); - - out: - free_trash_chunk(msg); - return ret; - - error: - ret = TCPCHK_EVAL_STOP; - msg = alloc_trash_chunk(); - if (msg) - tcpcheck_expect_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; -} - -/* Custom tcp-check expect function to parse and validate the agent-check - * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE - * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP; - enum healthcheck_status status = HCHK_STATUS_CHECKED; - const char *hs = NULL; /* health status */ - const char *as = NULL; /* admin status */ - const char *ps = NULL; /* performance status */ - const char *cs = NULL; /* maxconn */ - const char *err = NULL; /* first error to report */ - const char *wrn = NULL; /* first warning to report */ - char *cmd, *p; - - /* We're getting an agent check response. The agent could - * have been disabled in the mean time with a long check - * still pending. It is important that we ignore the whole - * response. - */ - if (!(check->state & CHK_ST_ENABLED)) - goto out; - - /* The agent supports strings made of a single line ended by the - * first CR ('\r') or LF ('\n'). This line is composed of words - * delimited by spaces (' '), tabs ('\t'), or commas (','). The - * line may optionally contained a description of a state change - * after a sharp ('#'), which is only considered if a health state - * is announced. - * - * Words may be composed of : - * - a numeric weight suffixed by the percent character ('%'). - * - a health status among "up", "down", "stopped", and "fail". - * - an admin status among "ready", "drain", "maint". - * - * These words may appear in any order. If multiple words of the - * same category appear, the last one wins. - */ - - p = b_head(&check->bi); - while (*p && *p != '\n' && *p != '\r') - p++; - - if (!*p) { - if (!last_read) - goto wait_more_data; - - /* at least inform the admin that the agent is mis-behaving */ - set_server_check_status(check, check->status, "Ignoring incomplete line from agent"); - goto out; - } - - *p = 0; - cmd = b_head(&check->bi); - - while (*cmd) { - /* look for next word */ - if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') { - cmd++; - continue; - } - - if (*cmd == '#') { - /* this is the beginning of a health status description, - * skip the sharp and blanks. - */ - cmd++; - while (*cmd == '\t' || *cmd == ' ') - cmd++; - break; - } - - /* find the end of the word so that we have a null-terminated - * word between and

. - */ - p = cmd + 1; - while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',') - p++; - if (*p) - *p++ = 0; - - /* first, health statuses */ - if (strcasecmp(cmd, "up") == 0) { - check->server->check.health = check->server->check.rise + check->server->check.fall - 1; - status = HCHK_STATUS_L7OKD; - hs = cmd; - } - else if (strcasecmp(cmd, "down") == 0) { - check->server->check.health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - else if (strcasecmp(cmd, "stopped") == 0) { - check->server->check.health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - else if (strcasecmp(cmd, "fail") == 0) { - check->server->check.health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - /* admin statuses */ - else if (strcasecmp(cmd, "ready") == 0) { - as = cmd; - } - else if (strcasecmp(cmd, "drain") == 0) { - as = cmd; - } - else if (strcasecmp(cmd, "maint") == 0) { - as = cmd; - } - /* try to parse a weight here and keep the last one */ - else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) { - ps = cmd; - } - /* try to parse a maxconn here */ - else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) { - cs = cmd; - } - else { - /* keep a copy of the first error */ - if (!err) - err = cmd; - } - /* skip to next word */ - cmd = p; - } - /* here, cmd points either to \0 or to the beginning of a - * description. Skip possible leading spaces. - */ - while (*cmd == ' ' || *cmd == '\n') - cmd++; - - /* First, update the admin status so that we avoid sending other - * possibly useless warnings and can also update the health if - * present after going back up. - */ - if (as) { - if (strcasecmp(as, "drain") == 0) - srv_adm_set_drain(check->server); - else if (strcasecmp(as, "maint") == 0) - srv_adm_set_maint(check->server); - else - srv_adm_set_ready(check->server); - } - - /* now change weights */ - if (ps) { - const char *msg; - - msg = server_parse_weight_change_request(check->server, ps); - if (!wrn || !*wrn) - wrn = msg; - } - - if (cs) { - const char *msg; - - cs += strlen("maxconn:"); - - msg = server_parse_maxconn_change_request(check->server, cs); - if (!wrn || !*wrn) - wrn = msg; - } - - /* and finally health status */ - if (hs) { - /* We'll report some of the warnings and errors we have - * here. Down reports are critical, we leave them untouched. - * Lack of report, or report of 'UP' leaves the room for - * ERR first, then WARN. - */ - const char *msg = cmd; - struct buffer *t; - - if (!*msg || status == HCHK_STATUS_L7OKD) { - if (err && *err) - msg = err; - else if (wrn && *wrn) - msg = wrn; - } - - t = get_trash_chunk(); - chunk_printf(t, "via agent : %s%s%s%s", - hs, *msg ? " (" : "", - msg, *msg ? ")" : ""); - set_server_check_status(check, status, t->area); - } - else if (err && *err) { - /* No status change but we'd like to report something odd. - * Just report the current state and copy the message. - */ - chunk_printf(&trash, "agent reports an error : %s", err); - set_server_check_status(check, status/*check->status*/, trash.area); - } - else if (wrn && *wrn) { - /* No status change but we'd like to report something odd. - * Just report the current state and copy the message. - */ - chunk_printf(&trash, "agent warns : %s", wrn); - set_server_check_status(check, status/*check->status*/, trash.area); - } - else - set_server_check_status(check, status, NULL); - - out: - return ret; - - wait_more_data: - ret = TCPCHK_EVAL_WAIT; - goto out; -} - -/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the - * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or - * TCPCHK_EVAL_STOP if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - struct tcpcheck_connect *connect = &rule->connect; - struct proxy *proxy = check->proxy; - struct server *s = check->server; - struct task *t = check->task; - struct conn_stream *cs; - struct connection *conn = NULL; - struct protocol *proto; - struct xprt_ops *xprt; - struct tcpcheck_rule *next; - int status, port; - - /* For a connect action we'll create a new connection. We may also have - * to kill a previous one. But we don't want to leave *without* a - * connection if we came here from the connection layer, hence with a - * connection. Thus we'll proceed in the following order : - * 1: close but not release previous connection (handled by the caller) - * 2: try to get a new connection - * 3: release and replace the old one on success - */ - - /* 2- prepare new connection */ - cs = cs_new(NULL); - if (!cs) { - chunk_printf(&trash, "TCPCHK error allocating connection at step %d", - tcpcheck_get_step_id(check, rule)); - if (rule->comment) - chunk_appendf(&trash, " comment: '%s'", rule->comment); - set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area); - ret = TCPCHK_EVAL_STOP; - goto out; - } - - /* 3- release and replace the old one on success */ - if (check->cs) { - if (check->wait_list.events) - check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events, - &check->wait_list); - - /* We may have been scheduled to run, and the I/O handler - * expects to have a cs, so remove the tasklet - */ - tasklet_remove_from_tasklet_list(check->wait_list.tasklet); - cs_destroy(check->cs); - } - - tasklet_set_tid(check->wait_list.tasklet, tid); - - check->cs = cs; - conn = cs->conn; - conn_set_owner(conn, check->sess, NULL); - - /* Maybe there were an older connection we were waiting on */ - check->wait_list.events = 0; - conn->target = s ? &s->obj_type : &proxy->obj_type; - - /* no client address */ - if (!sockaddr_alloc(&conn->dst)) { - status = SF_ERR_RESOURCE; - goto fail_check; - } - - /* connect to the connect rule addr if specified, otherwise the check - * addr if specified on the server. otherwise, use the server addr (it - * MUST exist at this step). - */ - *conn->dst = (is_addr(&connect->addr) - ? connect->addr - : (is_addr(&check->addr) ? check->addr : s->addr)); - proto = protocol_by_family(conn->dst->ss_family); - - port = 0; - if (!port && connect->port) - port = connect->port; - if (!port && connect->port_expr) { - struct sample *smp; - - smp = sample_fetch_as_type(check->proxy, check->sess, NULL, - SMP_OPT_DIR_REQ | SMP_OPT_FINAL, - connect->port_expr, SMP_T_SINT); - if (smp) - port = smp->data.u.sint; - } - if (!port && is_inet_addr(&connect->addr)) - port = get_host_port(&connect->addr); - if (!port && check->port) - port = check->port; - if (!port && is_inet_addr(&check->addr)) - port = get_host_port(&check->addr); - if (!port) { - /* The server MUST exist here */ - port = s->svc_port; - } - set_host_port(conn->dst, port); - - xprt = ((connect->options & TCPCHK_OPT_SSL) - ? xprt_get(XPRT_SSL) - : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW))); - - conn_prepare(conn, proto, xprt); - cs_attach(cs, check, &check_conn_cb); - - status = SF_ERR_INTERNAL; - next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule); - if (proto && proto->connect) { - int flags = 0; - - if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) - flags |= CONNECT_HAS_DATA; - if (!next || next->action != TCPCHK_ACT_EXPECT) - flags |= CONNECT_DELACK_ALWAYS; - status = proto->connect(conn, flags); - } - - if (status != SF_ERR_NONE) - goto fail_check; - - conn->flags |= CO_FL_PRIVATE; - conn->ctx = cs; - - /* The mux may be initialized now if there isn't server attached to the - * check (email alerts) or if there is a mux proto specified or if there - * is no alpn. - */ - if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) || - connect->mux_proto || (!connect->alpn && !check->alpn_str)) { - const struct mux_ops *mux_ops; - - if (connect->mux_proto) - mux_ops = connect->mux_proto->mux; - else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) - mux_ops = check->mux_proto->mux; - else { - int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK - ? PROTO_MODE_HTTP - : PROTO_MODE_TCP); - - mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode); - } - if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) { - status = SF_ERR_INTERNAL; - goto fail_check; - } - } - -#ifdef USE_OPENSSL - if (connect->sni) - ssl_sock_set_servername(conn, connect->sni); - else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni) - ssl_sock_set_servername(conn, s->check.sni); - - if (connect->alpn) - ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len); - else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str) - ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len); -#endif - if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) { - conn->send_proxy_ofs = 1; - conn->flags |= CO_FL_SOCKS4; - } - else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) { - conn->send_proxy_ofs = 1; - conn->flags |= CO_FL_SOCKS4; - } - - if (connect->options & TCPCHK_OPT_SEND_PROXY) { - conn->send_proxy_ofs = 1; - conn->flags |= CO_FL_SEND_PROXY; - } - else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) { - conn->send_proxy_ofs = 1; - conn->flags |= CO_FL_SEND_PROXY; - } - - if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) { - /* Some servers don't like reset on close */ - fdtab[cs->conn->handle.fd].linger_risk = 0; - } - - if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) { - if (xprt_add_hs(conn) < 0) - status = SF_ERR_RESOURCE; - } - - fail_check: - /* It can return one of : - * - SF_ERR_NONE if everything's OK - * - SF_ERR_SRVTO if there are no more servers - * - SF_ERR_SRVCL if the connection was refused by the server - * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) - * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) - * - SF_ERR_INTERNAL for any other purely internal errors - * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted. - * Note that we try to prevent the network stack from sending the ACK during the - * connect() when a pure TCP check is used (without PROXY protocol). - */ - switch (status) { - case SF_ERR_NONE: - /* 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 - */ - t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter)); - - if (proxy->timeout.check && proxy->timeout.connect) { - int t_con = tick_add(now_ms, proxy->timeout.connect); - t->expire = tick_first(t->expire, t_con); - } - break; - case SF_ERR_SRVTO: /* ETIMEDOUT */ - case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */ - case SF_ERR_PRXCOND: - case SF_ERR_RESOURCE: - case SF_ERR_INTERNAL: - chk_report_conn_err(check, errno, 0); - ret = TCPCHK_EVAL_STOP; - goto out; - } - - /* don't do anything until the connection is established */ - if (conn->flags & CO_FL_WAIT_XPRT) { - if (conn->mux) { - if (next && next->action == TCPCHK_ACT_SEND) - conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); - else - conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); - } - ret = TCPCHK_EVAL_WAIT; - goto out; - } - - out: - if (conn && check->result == CHK_RES_FAILED) - conn->flags |= CO_FL_ERROR; - return ret; -} - -/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data - * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or - * TCPCHK_EVAL_STOP if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - struct tcpcheck_send *send = &rule->send; - struct conn_stream *cs = check->cs; - struct connection *conn = cs_conn(cs); - struct buffer *tmp = NULL; - struct htx *htx = NULL; - - /* reset the read & write buffer */ - b_reset(&check->bi); - b_reset(&check->bo); - - switch (send->type) { - case TCPCHK_SEND_STRING: - case TCPCHK_SEND_BINARY: - if (istlen(send->data) >= b_size(&check->bo)) { - chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d", - (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo), - tcpcheck_get_step_id(check, rule)); - set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); - ret = TCPCHK_EVAL_STOP; - goto out; - } - b_putist(&check->bo, send->data); - break; - case TCPCHK_SEND_STRING_LF: - check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt); - if (!b_data(&check->bo)) - goto out; - break; - case TCPCHK_SEND_BINARY_LF: { - int len = b_size(&check->bo); - - tmp = alloc_trash_chunk(); - if (!tmp) - goto error_lf; - tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt); - if (!b_data(tmp)) - goto out; - tmp->area[tmp->data] = '\0'; - if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0) - goto error_lf; - check->bo.data = len; - break; - } - case TCPCHK_SEND_HTTP: { - struct htx_sl *sl; - struct ist meth, uri, vsn, clen, body; - unsigned int slflags = 0; - - tmp = alloc_trash_chunk(); - if (!tmp) - goto error_htx; - - 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]); - if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) { - tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt); - uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/")); - } - else - uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); - vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0")); - - if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') || - (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))) - slflags |= HTX_SL_F_VER_11; - slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN); - if (!isttest(send->http.body)) - slflags |= HTX_SL_F_BODYLESS; - - htx = htx_from_buf(&check->bo); - sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn); - if (!sl) - goto error_htx; - sl->info.req.meth = send->http.meth.meth; - if (!http_update_host(htx, sl, uri)) - goto error_htx; - - if (!LIST_ISEMPTY(&send->http.hdrs)) { - struct tcpcheck_http_hdr *hdr; - struct ist hdr_value; - - 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; - hdr_value = ist2(b_orig(tmp), b_data(tmp)); - if (!htx_add_header(htx, hdr->name, hdr_value)) - goto error_htx; - if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) { - if (!http_update_authority(htx, sl, hdr_value)) - goto error_htx; - } - } - - } - if (check->proxy->options2 & PR_O2_CHK_SNDST) { - chunk_reset(tmp); - httpchk_build_status_header(check->server, tmp); - if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp)))) - goto error_htx; - } - - - if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) { - chunk_reset(tmp); - tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt); - body = ist2(b_orig(tmp), b_data(tmp)); - } - else - body = send->http.body; - clen = ist((!istlen(body) ? "0" : ultoa(istlen(body)))); - - if (!htx_add_header(htx, ist("Connection"), ist("close")) || - !htx_add_header(htx, ist("Content-length"), clen)) - goto error_htx; - - - if (!htx_add_endof(htx, HTX_BLK_EOH) || - (istlen(body) && !htx_add_data_atonce(htx, body)) || - !htx_add_endof(htx, HTX_BLK_EOM)) - goto error_htx; - - htx_to_buf(htx, &check->bo); - break; - } - case TCPCHK_SEND_UNDEF: - /* Should never happen. */ - ret = TCPCHK_EVAL_STOP; - goto out; - }; - - - if (conn->mux->snd_buf(cs, &check->bo, - (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) { - if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) { - ret = TCPCHK_EVAL_STOP; - goto out; - } - } - if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) { - cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); - ret = TCPCHK_EVAL_WAIT; - goto out; - } - - out: - free_trash_chunk(tmp); - return ret; - - error_htx: - if (htx) { - htx_reset(htx); - htx_to_buf(htx, &check->bo); - } - chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d", - tcpcheck_get_step_id(check, rule)); - set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); - ret = TCPCHK_EVAL_STOP; - goto out; - - error_lf: - chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d", - tcpcheck_get_step_id(check, rule)); - set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); - ret = TCPCHK_EVAL_STOP; - goto out; - -} - -/* Try to receive data before evaluating a tcp-check expect rule. Returns - * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing - * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or - * TCPCHK_EVAL_STOP if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule) -{ - struct conn_stream *cs = check->cs; - struct connection *conn = cs_conn(cs); - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - size_t max, read, cur_read = 0; - int is_empty; - int read_poll = MAX_READ_POLL_LOOPS; - - if (check->wait_list.events & SUB_RETRY_RECV) - goto wait_more_data; - - if (cs->flags & CS_FL_EOS) - goto end_recv; - - /* errors on the connection and the conn-stream were already checked */ - - /* prepare to detect if the mux needs more room */ - cs->flags &= ~CS_FL_WANT_ROOM; - - while ((cs->flags & CS_FL_RCV_MORE) || - (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) { - max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi)); - read = conn->mux->rcv_buf(cs, &check->bi, max, 0); - cur_read += read; - if (!read || - (cs->flags & CS_FL_WANT_ROOM) || - (--read_poll <= 0) || - (read < max && read >= global.tune.recv_enough)) - break; - } - - end_recv: - is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi)); - if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) { - /* 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. - */ - goto stop; - } - if (!cur_read) { - if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) { - conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); - goto wait_more_data; - } - if (is_empty) { - int status; - - chunk_printf(&trash, "TCPCHK got an empty response at step %d", - tcpcheck_get_step_id(check, rule)); - if (rule->comment) - chunk_appendf(&trash, " comment: '%s'", rule->comment); - - status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP); - set_server_check_status(check, status, trash.area); - goto stop; - } - } - - out: - return ret; - - stop: - ret = TCPCHK_EVAL_STOP; - goto out; - - wait_more_data: - ret = TCPCHK_EVAL_WAIT; - goto out; -} - -/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If is set , no more data - * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data, - * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an - * error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read) -{ - struct htx *htx = htxbuf(&check->bi); - struct htx_sl *sl; - struct htx_blk *blk; - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - struct tcpcheck_expect *expect = &rule->expect; - struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL; - enum healthcheck_status status = HCHK_STATUS_L7RSP; - struct ist desc = IST_NULL; - int i, match, inverse; - - last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM)); - - if (htx->flags & HTX_FL_PARSING_ERROR) { - status = HCHK_STATUS_L7RSP; - goto error; - } - - if (htx_is_empty(htx)) { - if (last_read) { - status = HCHK_STATUS_L7RSP; - goto error; - } - goto wait_more_data; - } - - sl = http_get_stline(htx); - check->code = sl->info.res.status; - - 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; - status = expect->err_status; - - switch (expect->type) { - case TCPCHK_EXPECT_HTTP_STATUS: - match = 0; - for (i = 0; i < expect->codes.num; i++) { - if (sl->info.res.status >= expect->codes.codes[i][0] && - sl->info.res.status <= expect->codes.codes[i][1]) { - match = 1; - break; - } - } - - /* Set status and description in case of error */ - status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); - if (LIST_ISEMPTY(&expect->onerror_fmt)) - desc = htx_sl_res_reason(sl); - break; - case TCPCHK_EXPECT_HTTP_STATUS_REGEX: - match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); - - /* Set status and description in case of error */ - status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); - if (LIST_ISEMPTY(&expect->onerror_fmt)) - desc = htx_sl_res_reason(sl); - break; - - case TCPCHK_EXPECT_HTTP_HEADER: { - struct http_hdr_ctx ctx; - struct ist npat, vpat, value; - int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL)); - - if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { - nbuf = alloc_trash_chunk(); - if (!nbuf) { - status = HCHK_STATUS_L7RSP; - desc = ist("Failed to allocate buffer to eval log-format string"); - goto error; - } - nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt); - if (!b_data(nbuf)) { - status = HCHK_STATUS_L7RSP; - desc = ist("log-format string evaluated to an empty string"); - goto error; - } - npat = ist2(b_orig(nbuf), b_data(nbuf)); - } - else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)) - npat = expect->hdr.name; - - if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { - vbuf = alloc_trash_chunk(); - if (!vbuf) { - status = HCHK_STATUS_L7RSP; - desc = ist("Failed to allocate buffer to eval log-format string"); - goto error; - } - vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt); - if (!b_data(vbuf)) { - status = HCHK_STATUS_L7RSP; - desc = ist("log-format string evaluated to an empty string"); - goto error; - } - vpat = ist2(b_orig(vbuf), b_data(vbuf)); - } - else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)) - vpat = expect->hdr.value; - - match = 0; - ctx.blk = NULL; - while (1) { - switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) { - case TCPCHK_EXPT_FL_HTTP_HNAME_STR: - if (!http_find_str_header(htx, npat, &ctx, full)) - goto end_of_match; - break; - case TCPCHK_EXPT_FL_HTTP_HNAME_BEG: - if (!http_find_pfx_header(htx, npat, &ctx, full)) - goto end_of_match; - break; - case TCPCHK_EXPT_FL_HTTP_HNAME_END: - if (!http_find_sfx_header(htx, npat, &ctx, full)) - goto end_of_match; - break; - case TCPCHK_EXPT_FL_HTTP_HNAME_SUB: - if (!http_find_sub_header(htx, npat, &ctx, full)) - goto end_of_match; - break; - case TCPCHK_EXPT_FL_HTTP_HNAME_REG: - if (!http_match_header(htx, expect->hdr.name_re, &ctx, full)) - goto end_of_match; - break; - default: - /* should never happen */ - goto end_of_match; - } - - /* A header has matched the name pattern, let's test its - * value now (always defined from there). If there is no - * value pattern, it is a good match. - */ - - if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) { - match = 1; - goto end_of_match; - } - - value = ctx.value; - switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) { - case TCPCHK_EXPT_FL_HTTP_HVAL_STR: - if (isteq(value, vpat)) { - match = 1; - goto end_of_match; - } - break; - case TCPCHK_EXPT_FL_HTTP_HVAL_BEG: - if (istlen(value) < istlen(vpat)) - break; - value = ist2(istptr(value), istlen(vpat)); - if (isteq(value, vpat)) { - match = 1; - goto end_of_match; - } - break; - case TCPCHK_EXPT_FL_HTTP_HVAL_END: - if (istlen(value) < istlen(vpat)) - break; - value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat)); - if (isteq(value, vpat)) { - match = 1; - goto end_of_match; - } - break; - case TCPCHK_EXPT_FL_HTTP_HVAL_SUB: - if (isttest(istist(value, vpat))) { - match = 1; - goto end_of_match; - } - break; - case TCPCHK_EXPT_FL_HTTP_HVAL_REG: - if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) { - match = 1; - goto end_of_match; - } - break; - } - } - - end_of_match: - status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); - if (LIST_ISEMPTY(&expect->onerror_fmt)) - desc = htx_sl_res_reason(sl); - break; - } - - case TCPCHK_EXPECT_HTTP_BODY: - case TCPCHK_EXPECT_HTTP_BODY_REGEX: - case TCPCHK_EXPECT_HTTP_BODY_LF: - match = 0; - chunk_reset(&trash); - for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) { - enum htx_blk_type type = htx_get_blk_type(blk); - - if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT) - break; - if (type == HTX_BLK_DATA) { - if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk))) - break; - } - } - - if (!b_data(&trash)) { - if (!last_read) - goto wait_more_data; - status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP); - if (LIST_ISEMPTY(&expect->onerror_fmt)) - desc = ist("HTTP content check could not find a response body"); - goto error; - } - - if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) { - tmp = alloc_trash_chunk(); - if (!tmp) { - status = HCHK_STATUS_L7RSP; - desc = ist("Failed to allocate buffer to eval log-format string"); - goto error; - } - tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt); - if (!b_data(tmp)) { - status = HCHK_STATUS_L7RSP; - desc = ist("log-format string evaluated to an empty string"); - goto error; - } - } - - if (!last_read && - ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) || - ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) || - (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) { - ret = TCPCHK_EVAL_WAIT; - goto out; - } - - if (expect->type ==TCPCHK_EXPECT_HTTP_BODY) - match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL; - else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF) - match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL; - else - match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash)); - - /* Set status and description in case of error */ - status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP); - if (LIST_ISEMPTY(&expect->onerror_fmt)) - desc = (inverse - ? ist("HTTP check matched unwanted content") - : ist("HTTP content check did not match")); - break; - - - default: - /* should never happen */ - status = ((status != HCHK_STATUS_UNKNOWN) ? 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(tmp); - free_trash_chunk(nbuf); - free_trash_chunk(vbuf); - free_trash_chunk(msg); - return ret; - - error: - ret = TCPCHK_EVAL_STOP; - msg = alloc_trash_chunk(); - if (msg) - tcpcheck_expect_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; -} - -/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for - * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP - * if an error occurred. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_expect(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, *tmp = NULL; - struct ist desc = IST_NULL; - enum healthcheck_status status; - 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. - */ - if (!last_read) { - if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) && - (b_data(&check->bi) < istlen(expect->data))) { - ret = TCPCHK_EVAL_WAIT; - goto out; - } - if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) { - ret = TCPCHK_EVAL_WAIT; - goto out; - } - } - - inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV); - /* Make GCC happy ; initialize match to a failure state. */ - match = inverse; - status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP); - - switch (expect->type) { - case TCPCHK_EXPECT_STRING: - case TCPCHK_EXPECT_BINARY: - match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL; - break; - case TCPCHK_EXPECT_STRING_REGEX: - match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1)); - break; - - case TCPCHK_EXPECT_BINARY_REGEX: - chunk_reset(&trash); - dump_binary(&trash, b_head(&check->bi), b_data(&check->bi)); - match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1)); - break; - - case TCPCHK_EXPECT_STRING_LF: - case TCPCHK_EXPECT_BINARY_LF: - match = 0; - tmp = alloc_trash_chunk(); - if (!tmp) { - status = HCHK_STATUS_L7RSP; - desc = ist("Failed to allocate buffer to eval format string"); - goto error; - } - tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt); - if (!b_data(tmp)) { - status = HCHK_STATUS_L7RSP; - desc = ist("log-format string evaluated to an empty string"); - goto error; - } - if (expect->type == TCPCHK_EXPECT_BINARY_LF) { - int len = tmp->data; - if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) { - status = HCHK_STATUS_L7RSP; - desc = ist("Failed to parse hexastring resulting of eval of a log-format string"); - goto error; - } - tmp->data = len; - } - if (b_data(&check->bi) < tmp->data) { - if (!last_read) { - ret = TCPCHK_EVAL_WAIT; - goto out; - } - break; - } - match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL; - break; - - case TCPCHK_EXPECT_CUSTOM: - if (expect->custom) - ret = expect->custom(check, rule, last_read); - goto out; - default: - /* Should never happen. */ - ret = TCPCHK_EVAL_STOP; - goto out; - } - - - /* 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; - } - - /* Result as expected, next rule. */ - if (match ^ inverse) - goto out; - - error: - /* From this point on, we matched something we did not want, this is an error state. */ - ret = TCPCHK_EVAL_STOP; - msg = alloc_trash_chunk(); - if (msg) - tcpcheck_expect_onerror_message(msg, check, rule, match, desc); - set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); - free_trash_chunk(msg); - - out: - free_trash_chunk(tmp); - return ret; -} - -/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to - * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never - * waits. - */ -static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule) -{ - enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; - struct act_rule *act_rule; - enum act_return act_ret; - - act_rule =rule->action_kw.rule; - act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0); - if (act_ret != ACT_RET_CONT) { - chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n", - tcpcheck_get_step_id(check, rule)); - set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); - ret = TCPCHK_EVAL_STOP; - } - - return ret; -} - -/* Executes a tcp-check ruleset. Note that this is called both from the - * connection's wake() callback and from the check scheduling task. It returns - * 0 on normal cases, or <0 if a close() has happened on an existing connection, - * presenting the risk of an fd replacement. - * - * Please do NOT place any return statement in this function and only leave - * via the out_end_tcpcheck label after setting retcode. - */ -static int tcpcheck_main(struct check *check) -{ - struct tcpcheck_rule *rule; - struct conn_stream *cs = check->cs; - struct connection *conn = cs_conn(cs); - int must_read = 1, last_read = 0; - int ret, retcode = 0; - enum tcpcheck_eval_ret eval_ret; - - /* here, we know that the check is complete or that it failed */ - if (check->result != CHK_RES_UNKNOWN) - goto out; - - /* Note: the conn-stream and the connection may only be undefined before - * the first rule evaluation (it is always a connect rule) or when the - * conn-stream allocation failed on the first connect. - */ - - /* 1- check for connection error, if any */ - if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) - goto out_end_tcpcheck; - - /* 2- check if we are waiting for the connection establishment. It only - * happens during TCPCHK_ACT_CONNECT. */ - if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) { - if (conn->flags & CO_FL_WAIT_XPRT) { - struct tcpcheck_rule *next; - - next = get_next_tcpcheck_rule(check->tcpcheck_rules, check->current_step); - if (next && next->action == TCPCHK_ACT_SEND) { - if (!(check->wait_list.events & SUB_RETRY_SEND)) - conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); - goto out; - } - else { - eval_ret = tcpcheck_eval_recv(check, check->current_step); - if (eval_ret == TCPCHK_EVAL_STOP) - goto out_end_tcpcheck; - else if (eval_ret == TCPCHK_EVAL_WAIT) - goto out; - last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS))); - must_read = 0; - } - } - rule = LIST_NEXT(&check->current_step->list, typeof(rule), list); - } - - /* 3- check for pending outgoing data. It only happens during - * TCPCHK_ACT_SEND. */ - else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) { - if (b_data(&check->bo)) { - /* We're already waiting to be able to send, give up */ - if (check->wait_list.events & SUB_RETRY_SEND) - goto out; - - ret = conn->mux->snd_buf(cs, &check->bo, - (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0); - if (ret <= 0) { - if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) - goto out_end_tcpcheck; - } - if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) { - conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); - goto out; - } - } - rule = LIST_NEXT(&check->current_step->list, typeof(rule), list); - } - - /* 4- check if a rule must be resume. It happens if check->current_step - * is defined. */ - else if (check->current_step) - rule = check->current_step; - - /* 5- It is the first evaluation. We must create a session and preset - * tcp-check variables */ - else { - struct tcpcheck_var *var; - - /* First evaluation, create a session */ - check->sess = session_new(&checks_fe, NULL, &check->obj_type); - if (!check->sess) { - chunk_printf(&trash, "TCPCHK error allocating check session"); - set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area); - goto out_end_tcpcheck; - } - vars_init(&check->vars, SCOPE_CHECK); - rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list); - - /* Preset tcp-check variables */ - list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) { - struct sample smp; - - memset(&smp, 0, sizeof(smp)); - smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL); - smp.data = var->data; - vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp); - } - } - - /* Now evaluate the tcp-check rules */ - - list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) { - check->code = 0; - switch (rule->action) { - case TCPCHK_ACT_CONNECT: - check->current_step = rule; - - /* close but not release yet previous connection */ - if (check->cs) { - cs_close(check->cs); - retcode = -1; /* do not reuse the fd in the caller! */ - } - eval_ret = tcpcheck_eval_connect(check, rule); - - /* Refresh conn-stream and connection */ - cs = check->cs; - conn = cs_conn(cs); - must_read = 1; last_read = 0; - break; - case TCPCHK_ACT_SEND: - check->current_step = rule; - eval_ret = tcpcheck_eval_send(check, rule); - must_read = 1; - break; - case TCPCHK_ACT_EXPECT: - check->current_step = rule; - if (must_read) { - if (check->proxy->timeout.check) - check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check); - - eval_ret = tcpcheck_eval_recv(check, rule); - if (eval_ret == TCPCHK_EVAL_STOP) - goto out_end_tcpcheck; - else if (eval_ret == TCPCHK_EVAL_WAIT) - goto out; - last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS))); - must_read = 0; - } - - 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; - if (!(check->wait_list.events & SUB_RETRY_RECV)) - conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); - } - break; - case TCPCHK_ACT_ACTION_KW: - /* Don't update the current step */ - eval_ret = tcpcheck_eval_action_kw(check, rule); - break; - default: - /* Otherwise, just go to the next one and don't update - * the current step - */ - eval_ret = TCPCHK_EVAL_CONTINUE; - break; - } - - switch (eval_ret) { - case TCPCHK_EVAL_CONTINUE: - break; - case TCPCHK_EVAL_WAIT: - goto out; - case TCPCHK_EVAL_STOP: - goto out_end_tcpcheck; - } - } - - /* All rules was evaluated */ - if (check->current_step) { - rule = check->current_step; - - if (rule->action == TCPCHK_ACT_EXPECT) { - struct buffer *msg; - enum healthcheck_status status; - - 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_expect_onsuccess_message(msg, check, rule, IST_NULL); - status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); - set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)")); - free_trash_chunk(msg); - } - else if (rule->action == TCPCHK_ACT_CONNECT) { - const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)"); - enum healthcheck_status status = HCHK_STATUS_L4OK; -#ifdef USE_OPENSSL - if (ssl_sock_is_ssl(conn)) - status = HCHK_STATUS_L6OK; -#endif - set_server_check_status(check, status, msg); - } - } - else - set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)"); - - out_end_tcpcheck: - if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) - chk_report_conn_err(check, errno, 0); - - out: - return retcode; -} - - /**************************************************************************/ /************** Health-checks based on an external process ****************/ /**************************************************************************/ @@ -3596,1346 +1523,6 @@ static struct task *process_chk_conn(struct task *t, void *context, unsigned sho } -/**************************************************************************/ -/******************* Internals to parse tcp-check rules *******************/ -/**************************************************************************/ -struct action_kw_list tcp_check_keywords = { - .list = LIST_HEAD_INIT(tcp_check_keywords.list), -}; - -/* Return the struct action_kw associated to a keyword */ -static struct action_kw *action_kw_tcp_check_lookup(const char *kw) -{ - return action_lookup(&tcp_check_keywords.list, kw); -} - -static void action_kw_tcp_check_build_list(struct buffer *chk) -{ - action_build_list(&tcp_check_keywords.list, chk); -} - -/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is - * returned on error. - */ -static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px, - struct list *rules, struct action_kw *kw, - const char *file, int line, char **errmsg) -{ - struct tcpcheck_rule *chk = NULL; - struct act_rule *actrule = NULL; - - actrule = calloc(1, sizeof(*actrule)); - if (!actrule) { - memprintf(errmsg, "out of memory"); - goto error; - } - actrule->kw = kw; - actrule->from = ACT_F_TCP_CHK; - - cur_arg++; - if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) { - memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg); - goto error; - } - - chk = calloc(1, sizeof(*chk)); - if (!chk) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->action = TCPCHK_ACT_ACTION_KW; - chk->action_kw.rule = actrule; - return chk; - - error: - free(actrule); - return NULL; -} - -/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is - * returned on error. - */ -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) -{ - struct tcpcheck_rule *chk = NULL; - struct sockaddr_storage *sk = NULL; - char *comment = NULL, *sni = NULL, *alpn = NULL; - struct sample_expr *port_expr = NULL; - const struct mux_proto_list *mux_proto = NULL; - unsigned short conn_opts = 0; - long port = 0; - int alpn_len = 0; - - list_for_each_entry(chk, rules, list) { - if (chk->action == TCPCHK_ACT_CONNECT) - break; - 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', " - "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', " - "when there is a 'connect' step in the tcp-check ruleset"); - goto error; - } - - cur_arg++; - while (*(args[cur_arg])) { - if (strcmp(args[cur_arg], "default") == 0) - conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT; - else if (strcmp(args[cur_arg], "addr") == 0) { - int port1, port2; - struct protocol *proto; - - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects as argument.", args[cur_arg]); - goto error; - } - - sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1); - if (!sk) { - memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg); - goto error; - } - - proto = protocol_by_family(sk->ss_family); - if (!proto || !proto->connect) { - memprintf(errmsg, "'%s' : connect() not supported for this address family.\n", - args[cur_arg]); - goto error; - } - - if (port1 != port2) { - memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n", - args[cur_arg], args[cur_arg+1]); - goto error; - } - - cur_arg++; - } - else if (strcmp(args[cur_arg], "port") == 0) { - const char *p, *end; - - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]); - goto error; - } - cur_arg++; - - port = 0; - release_sample_expr(port_expr); - p = args[cur_arg]; end = p + strlen(p); - port = read_uint(&p, end); - if (p != end) { - int idx = 0; - - px->conf.args.ctx = ARGC_SRV; - port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx, - file, line, errmsg, &px->conf.args, NULL); - - if (!port_expr) { - memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg); - goto error; - } - if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) { - memprintf(errmsg, "error detected while parsing port expression : " - " fetch method '%s' extracts information from '%s', " - "none of which is available here.\n", - args[cur_arg], sample_src_names(port_expr->fetch->use)); - goto error; - } - px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY); - } - else if (port > 65535 || port < 1) { - memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.", - args[cur_arg]); - goto error; - } - } - else if (strcmp(args[cur_arg], "proto") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]); - goto error; - } - mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1]))); - if (!mux_proto) { - memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]); - goto error; - } - cur_arg++; - } - 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 if (strcmp(args[cur_arg], "send-proxy") == 0) - conn_opts |= TCPCHK_OPT_SEND_PROXY; - else if (strcmp(args[cur_arg], "via-socks4") == 0) - conn_opts |= TCPCHK_OPT_SOCKS4; - else if (strcmp(args[cur_arg], "linger") == 0) - conn_opts |= TCPCHK_OPT_LINGER; -#ifdef USE_OPENSSL - else if (strcmp(args[cur_arg], "ssl") == 0) { - px->options |= PR_O_TCPCHK_SSL; - conn_opts |= TCPCHK_OPT_SSL; - } - else if (strcmp(args[cur_arg], "sni") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); - goto error; - } - cur_arg++; - free(sni); - sni = strdup(args[cur_arg]); - if (!sni) { - memprintf(errmsg, "out of memory"); - goto error; - } - } - else if (strcmp(args[cur_arg], "alpn") == 0) { -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - free(alpn); - if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) { - memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg); - goto error; - } - cur_arg++; -#else - memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]); - goto error; -#endif - } -#endif /* USE_OPENSSL */ - - else { - memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'" -#ifdef USE_OPENSSL - ", 'ssl', 'sni', 'alpn'" -#endif /* USE_OPENSSL */ - " or 'via-socks4', 'linger', 'default' but got '%s' as argument.", - args[cur_arg]); - goto error; - } - cur_arg++; - } - - chk = calloc(1, sizeof(*chk)); - if (!chk) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->action = TCPCHK_ACT_CONNECT; - chk->comment = comment; - chk->connect.port = port; - chk->connect.options = conn_opts; - chk->connect.sni = sni; - chk->connect.alpn = alpn; - chk->connect.alpn_len= alpn_len; - chk->connect.port_expr= port_expr; - chk->connect.mux_proto= mux_proto; - if (sk) - chk->connect.addr = *sk; - return chk; - - error: - free(alpn); - free(sni); - free(comment); - release_sample_expr(port_expr); - return NULL; -} - -/* Parses and creates a tcp-check send rule. NULL is returned on error */ -static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules, - const char *file, int line, char **errmsg) -{ - struct tcpcheck_rule *chk = NULL; - char *comment = NULL, *data = NULL; - enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF; - - if (strcmp(args[cur_arg], "send-binary-lf") == 0) - type = TCPCHK_SEND_BINARY_LF; - else if (strcmp(args[cur_arg], "send-binary") == 0) - type = TCPCHK_SEND_BINARY; - else if (strcmp(args[cur_arg], "send-lf") == 0) - type = TCPCHK_SEND_STRING_LF; - else if (strcmp(args[cur_arg], "send") == 0) - type = TCPCHK_SEND_STRING; - - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a %s as argument", - (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]); - goto error; - } - - data = args[cur_arg+1]; - - cur_arg += 2; - while (*(args[cur_arg])) { - 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' but got '%s' as argument.", - args[cur_arg]); - goto error; - } - cur_arg++; - } - - chk = calloc(1, sizeof(*chk)); - if (!chk) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->action = TCPCHK_ACT_SEND; - chk->comment = comment; - chk->send.type = type; - - switch (chk->send.type) { - case TCPCHK_SEND_STRING: - chk->send.data = ist2(strdup(data), strlen(data)); - if (!isttest(chk->send.data)) { - memprintf(errmsg, "out of memory"); - goto error; - } - break; - case TCPCHK_SEND_BINARY: { - int len = chk->send.data.len; - if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) { - memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg); - goto error; - } - chk->send.data.len = len; - break; - } - case TCPCHK_SEND_STRING_LF: - case TCPCHK_SEND_BINARY_LF: - LIST_INIT(&chk->send.fmt); - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg); - goto error; - } - break; - case TCPCHK_SEND_HTTP: - case TCPCHK_SEND_UNDEF: - goto error; - } - - return chk; - - error: - free(chk); - free(comment); - return NULL; -} - -/* Parses and creates a http-check send rule. NULL is returned on error */ -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, host_hdr = -1; - - 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 || strcmp(args[cur_arg], "uri-lf") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); - goto error; - } - flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT; - if (strcmp(args[cur_arg], "uri-lf") == 0) - flags |= TCPCHK_SND_HTTP_FL_URI_FMT; - cur_arg++; - uri = args[cur_arg]; - } - else if (strcmp(args[cur_arg], "ver") == 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; - } - - if (strcasecmp(args[cur_arg+1], "host") == 0) { - if (host_hdr >= 0) { - memprintf(errmsg, "'%s' header already defined (previous value is '%s')", - args[cur_arg+1], istptr(hdrs[host_hdr].v)); - goto error; - } - host_hdr = i; - } - else if (strcasecmp(args[cur_arg+1], "connection") == 0 || - strcasecmp(args[cur_arg+1], "content-length") == 0 || - strcasecmp(args[cur_arg+1], "transfer-encoding") == 0) - goto skip_hdr; - - 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++; - skip_hdr: - cur_arg += 2; - } - else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); - goto error; - } - flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT; - if (strcmp(args[cur_arg], "body-lf") == 0) - flags |= TCPCHK_SND_HTTP_FL_BODY_FMT; - cur_arg++; - body = args[cur_arg]; - } - 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', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'" - " 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) { - if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) { - LIST_INIT(&chk->send.http.uri_fmt); - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg); - goto error; - } - } - else { - 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; istlen(hdrs[i].n); i++) { - hdr = calloc(1, sizeof(*hdr)); - if (!hdr) { - memprintf(errmsg, "out of memory"); - goto error; - } - LIST_INIT(&hdr->value); - hdr->name = istdup(hdrs[i].n); - if (!isttest(hdr->name)) { - memprintf(errmsg, "out of memory"); - goto error; - } - - ist0(hdrs[i].v); - if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg)) - goto error; - LIST_ADDQ(&chk->send.http.hdrs, &hdr->list); - hdr = NULL; - } - - if (body) { - if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) { - LIST_INIT(&chk->send.http.body_fmt); - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg); - goto error; - } - } - else { - 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; -} - -/* Parses and creates a http-check comment rule. NULL is returned on error */ -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) -{ - struct tcpcheck_rule *chk = NULL; - char *comment = NULL; - - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "expects a string as argument"); - goto error; - } - cur_arg++; - comment = strdup(args[cur_arg]); - if (!comment) { - memprintf(errmsg, "out of memory"); - goto error; - } - - chk = calloc(1, sizeof(*chk)); - if (!chk) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->action = TCPCHK_ACT_COMMENT; - chk->comment = comment; - return chk; - - error: - free(comment); - return NULL; -} - -/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned - * on error. is set to the right protocol flags (covered by the - * TCPCHK_RULES_PROTO_CHK mask). - */ -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; - struct sample_expr *status_expr = NULL; - char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat; - enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF; - enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN; - enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN; - enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN; - unsigned int flags = 0; - long min_recv = -1; - int inverse = 0; - - on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL; - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "expects at least a matching pattern as arguments"); - goto error; - } - - cur_arg++; - while (*(args[cur_arg])) { - int in_pattern = 0; - - rescan: - if (strcmp(args[cur_arg], "min-recv") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]); - goto error; - } - /* Use an signed integer here because of chksize */ - cur_arg++; - min_recv = atol(args[cur_arg]); - if (min_recv < -1 || min_recv > INT_MAX) { - memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]); - goto error; - } - } - else if (*(args[cur_arg]) == '!') { - in_pattern = 1; - while (*(args[cur_arg]) == '!') { - inverse = !inverse; - args[cur_arg]++; - } - if (!*(args[cur_arg])) - cur_arg++; - goto rescan; - } - 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; - } - if (proto != TCPCHK_RULES_HTTP_CHK) - type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX); - else - type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX); - - 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_BINARY_REGEX); - - 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], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) { - if (type != TCPCHK_EXPECT_UNDEF) { - memprintf(errmsg, "only on pattern expected"); - goto error; - } - if (proto != TCPCHK_RULES_HTTP_CHK) - type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF); - else { - if (*(args[cur_arg]) != 's') - goto bad_http_kw; - type = TCPCHK_EXPECT_HTTP_BODY_LF; - } - - 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_STATUS_REGEX); - - 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], "custom") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (type != TCPCHK_EXPECT_UNDEF) { - memprintf(errmsg, "only on pattern expected"); - goto error; - } - type = TCPCHK_EXPECT_CUSTOM; - } - else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) { - int orig_arg = cur_arg; - - if (proto != TCPCHK_RULES_HTTP_CHK) - goto bad_tcp_kw; - if (type != TCPCHK_EXPECT_UNDEF) { - memprintf(errmsg, "only on pattern expected"); - goto error; - } - type = TCPCHK_EXPECT_HTTP_HEADER; - - if (strcmp(args[cur_arg], "fhdr") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL; - - /* Parse the name pattern, mandatory */ - if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) || - (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) { - memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern", - args[orig_arg]); - goto error; - } - - if (strcmp(args[cur_arg+1], "name-lf") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT; - - cur_arg += 2; - if (strcmp(args[cur_arg], "-m") == 0) { - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')", - args[orig_arg], args[cur_arg]); - goto error; - } - if (strcmp(args[cur_arg+1], "str") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR; - else if (strcmp(args[cur_arg+1], "beg") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG; - else if (strcmp(args[cur_arg+1], "end") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END; - else if (strcmp(args[cur_arg+1], "sub") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB; - else if (strcmp(args[cur_arg+1], "reg") == 0) { - if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { - memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method", - args[orig_arg]); - goto error; - } - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG; - } - else { - memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')", - args[orig_arg], args[cur_arg], args[cur_arg+1]); - goto error; - } - cur_arg += 2; - } - else - flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR; - npat = args[cur_arg]; - - if (!*(args[cur_arg+1]) || - (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) { - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE; - goto next; - } - if (strcmp(args[cur_arg+1], "value-lf") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT; - - /* Parse the value pattern, optional */ - if (strcmp(args[cur_arg+2], "-m") == 0) { - cur_arg += 2; - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')", - args[orig_arg], args[cur_arg]); - goto error; - } - if (strcmp(args[cur_arg+1], "str") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR; - else if (strcmp(args[cur_arg+1], "beg") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG; - else if (strcmp(args[cur_arg+1], "end") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END; - else if (strcmp(args[cur_arg+1], "sub") == 0) - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB; - else if (strcmp(args[cur_arg+1], "reg") == 0) { - if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { - memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method", - args[orig_arg]); - goto error; - } - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG; - } - else { - memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')", - args[orig_arg], args[cur_arg], args[cur_arg+1]); - goto error; - } - } - else - flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR; - - if (!*(args[cur_arg+2])) { - memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]); - goto error; - } - vpat = args[cur_arg+2]; - cur_arg += 2; - } - else if (strcmp(args[cur_arg], "comment") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - 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 if (strcmp(args[cur_arg], "on-success") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); - goto error; - } - cur_arg++; - on_success_msg = args[cur_arg]; - } - else if (strcmp(args[cur_arg], "on-error") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); - goto error; - } - cur_arg++; - on_error_msg = args[cur_arg]; - } - else if (strcmp(args[cur_arg], "ok-status") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); - goto error; - } - if (strcasecmp(args[cur_arg+1], "L7OK") == 0) - ok_st = HCHK_STATUS_L7OKD; - else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0) - ok_st = HCHK_STATUS_L7OKCD; - else if (strcasecmp(args[cur_arg+1], "L6OK") == 0) - ok_st = HCHK_STATUS_L6OK; - else if (strcasecmp(args[cur_arg+1], "L4OK") == 0) - ok_st = HCHK_STATUS_L4OK; - else { - memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').", - args[cur_arg], args[cur_arg+1]); - goto error; - } - cur_arg++; - } - else if (strcmp(args[cur_arg], "error-status") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); - goto error; - } - if (strcasecmp(args[cur_arg+1], "L7RSP") == 0) - err_st = HCHK_STATUS_L7RSP; - else if (strcasecmp(args[cur_arg+1], "L7STS") == 0) - err_st = HCHK_STATUS_L7STS; - else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0) - err_st = HCHK_STATUS_L6RSP; - else if (strcasecmp(args[cur_arg+1], "L4CON") == 0) - err_st = HCHK_STATUS_L4CON; - else { - memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').", - args[cur_arg], args[cur_arg+1]); - goto error; - } - cur_arg++; - } - else if (strcmp(args[cur_arg], "status-code") == 0) { - int idx = 0; - - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]); - goto error; - } - - cur_arg++; - release_sample_expr(status_expr); - px->conf.args.ctx = ARGC_SRV; - status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx, - file, line, errmsg, &px->conf.args, NULL); - if (!status_expr) { - memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg); - goto error; - } - if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) { - memprintf(errmsg, "error detected while parsing status-code expression : " - " fetch method '%s' extracts information from '%s', " - "none of which is available here.\n", - args[cur_arg], sample_src_names(status_expr->fetch->use)); - goto error; - } - px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY); - } - else if (strcmp(args[cur_arg], "tout-status") == 0) { - if (in_pattern) { - memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); - goto error; - } - if (!*(args[cur_arg+1])) { - memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); - goto error; - } - if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0) - tout_st = HCHK_STATUS_L7TOUT; - else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0) - tout_st = HCHK_STATUS_L6TOUT; - else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0) - tout_st = HCHK_STATUS_L4TOUT; - else { - memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').", - args[cur_arg], args[cur_arg+1]); - goto error; - } - cur_arg++; - } - else { - if (proto == TCPCHK_RULES_HTTP_CHK) { - bad_http_kw: - memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', " - "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]); - } - else { - bad_tcp_kw: - memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'" - "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]); - } - goto error; - } - next: - cur_arg++; - } - - chk = calloc(1, sizeof(*chk)); - if (!chk) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->action = TCPCHK_ACT_EXPECT; - LIST_INIT(&chk->expect.onerror_fmt); - LIST_INIT(&chk->expect.onsuccess_fmt); - chk->comment = comment; comment = NULL; - chk->expect.type = type; - chk->expect.min_recv = min_recv; - chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0); - chk->expect.ok_status = ok_st; - chk->expect.err_status = err_st; - chk->expect.tout_status = tout_st; - chk->expect.status_expr = status_expr; status_expr = NULL; - - if (on_success_msg) { - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg); - goto error; - } - } - if (on_error_msg) { - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg); - goto error; - } - } - - switch (chk->expect.type) { - case TCPCHK_EXPECT_HTTP_STATUS: { - const char *p = pattern; - unsigned int c1,c2; - - chk->expect.codes.codes = NULL; - chk->expect.codes.num = 0; - while (1) { - c1 = c2 = read_uint(&p, pattern + strlen(pattern)); - if (*p == '-') { - p++; - c2 = read_uint(&p, pattern + strlen(pattern)); - } - if (c1 > c2) { - memprintf(errmsg, "invalid range of status codes '%s'", pattern); - goto error; - } - - chk->expect.codes.num++; - chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes, - chk->expect.codes.num * sizeof(*chk->expect.codes.codes)); - if (!chk->expect.codes.codes) { - memprintf(errmsg, "out of memory"); - goto error; - } - chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1; - chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2; - - if (*p == '\0') - break; - if (*p != ',') { - memprintf(errmsg, "invalid character '%c' in the list of status codes", *p); - goto error; - } - p++; - } - break; - } - case TCPCHK_EXPECT_STRING: - case TCPCHK_EXPECT_HTTP_BODY: - chk->expect.data = ist2(strdup(pattern), strlen(pattern)); - if (!isttest(chk->expect.data)) { - memprintf(errmsg, "out of memory"); - goto error; - } - break; - case TCPCHK_EXPECT_BINARY: { - int len = chk->expect.data.len; - - if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) { - memprintf(errmsg, "invalid binary string (%s)", *errmsg); - goto error; - } - chk->expect.data.len = len; - break; - } - case TCPCHK_EXPECT_STRING_REGEX: - case TCPCHK_EXPECT_BINARY_REGEX: - case TCPCHK_EXPECT_HTTP_STATUS_REGEX: - case TCPCHK_EXPECT_HTTP_BODY_REGEX: - chk->expect.regex = regex_comp(pattern, 1, 0, errmsg); - if (!chk->expect.regex) - goto error; - break; - - case TCPCHK_EXPECT_STRING_LF: - case TCPCHK_EXPECT_BINARY_LF: - case TCPCHK_EXPECT_HTTP_BODY_LF: - LIST_INIT(&chk->expect.fmt); - px->conf.args.ctx = ARGC_SRV; - if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg); - goto error; - } - break; - - case TCPCHK_EXPECT_HTTP_HEADER: - if (!npat) { - memprintf(errmsg, "unexpected error, undefined header name pattern"); - goto error; - } - if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) { - chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg); - if (!chk->expect.hdr.name_re) - goto error; - } - else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { - px->conf.args.ctx = ARGC_SRV; - LIST_INIT(&chk->expect.hdr.name_fmt); - if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg); - goto error; - } - } - else { - chk->expect.hdr.name = ist2(strdup(npat), strlen(npat)); - if (!isttest(chk->expect.hdr.name)) { - memprintf(errmsg, "out of memory"); - goto error; - } - } - - if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) { - chk->expect.hdr.value = IST_NULL; - break; - } - - if (!vpat) { - memprintf(errmsg, "unexpected error, undefined header value pattern"); - goto error; - } - else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) { - chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg); - if (!chk->expect.hdr.value_re) - goto error; - } - else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { - px->conf.args.ctx = ARGC_SRV; - LIST_INIT(&chk->expect.hdr.value_fmt); - if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { - memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg); - goto error; - } - } - else { - chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat)); - if (!isttest(chk->expect.hdr.value)) { - memprintf(errmsg, "out of memory"); - goto error; - } - } - - break; - case TCPCHK_EXPECT_CUSTOM: - chk->expect.custom = NULL; /* Must be defined by the caller ! */ - break; - case TCPCHK_EXPECT_UNDEF: - memprintf(errmsg, "pattern not found"); - goto error; - } - - /* All tcp-check expect points back to the first inverse expect rule in - * a chain of one or more expect rule, potentially itself. - */ - chk->expect.head = chk; - list_for_each_entry_rev(prev_check, rules, list) { - if (prev_check->action == TCPCHK_ACT_EXPECT) { - if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV) - chk->expect.head = prev_check; - continue; - } - if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW) - break; - } - return chk; - - error: - free_tcpcheck(chk, 0); - free(comment); - release_sample_expr(status_expr); - return NULL; -} - -/* Overwrites fields of the old http send rule with those of the new one. When - * replaced, old values are freed and replaced by the new ones. New values are - * not copied but transferred. At the end should be empty and can be - * safely released. This function never fails. - */ -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)) - istfree(&old->send.http.uri); - 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)) - istfree(&old->send.http.uri); - 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)) { - istfree(&old->send.http.vsn); - 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)) - istfree(&old->send.http.body); - 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)) - istfree(&old->send.http.body); - 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); - } - } -} - -/* Internal function used to add an http-check rule in a list during the config - * parsing step. Depending on its type, and the previously inserted rules, a - * specific action may be performed or an error may be reported. This functions - * returns 1 on success and 0 on error and is filled with the error - * message. - */ -static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg) -{ - struct tcpcheck_rule *r; - - /* the implicit send rule coming from an "option httpchk" line must be - * merged with the first explici http-check send rule, if - * any. Depdending the declaration order some tests are required. - * - * Some tests is also required for other kinds of http-check rules to be - * sure the ruleset remains valid. - */ - - if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) { - /* Tries to add an implicit http-check send rule from an "option httpchk" line. - * First, the first rule is retrieved, skipping the first CONNECT, if any, and - * following tests are performed : - * - * 1- If there is no such rule or if it is not a send rule, the implicit send - * rule is pushed in front of the ruleset - * - * 2- If it is another implicit send rule, it is replaced with the new one. - * - * 3- Otherwise, it means it is an explicit send rule. In this case we merge - * both, overwriting the old send rule (the explicit one) with info of the - * new send rule (the implicit one). - */ - 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 { - /* Tries to add an explicit http-check rule. First of all we check the typefo the - * last inserted rule to be sure it is valid. Then for send rule, we try to merge it - * with an existing implicit send rule, if any. At the end, if there is no error, - * the rule is appended to the list. - */ - - 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 && r->action != TCPCHK_ACT_EXPECT && 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; -} - /**************************************************************************/ /************************** Init/deinit checks ****************************/ /**************************************************************************/ @@ -5359,133 +1946,6 @@ static int init_srv_agent_check(struct server *srv) return ret; } -/* Check tcp-check health-check configuration for the proxy . */ -static int check_proxy_tcpcheck(struct proxy *px) -{ - struct tcpcheck_rule *chk, *back; - 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) { - 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); - ret |= ERR_ALERT | ERR_FATAL; - goto out; - } - - /* HTTP ruleset only : */ - if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) { - struct tcpcheck_rule *next; - - /* move remaining implicit send rule from "option httpchk" line to the right place. - * If such rule exists, it must be the first one. In this case, the rule is moved - * after the first connect rule, if any. Otherwise, nothing is done. - */ - 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. It is inherited from previous - * versions where the http expect rule was optional. Now it is possible to chained - * send/expect rules but the last expect may still be implicit. - */ - chk = get_last_tcpcheck_rule(&px->tcpcheck_rules); - if (chk && chk->action == TCPCHK_ACT_SEND) { - next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""}, - 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; - } - } - - /* For all ruleset: */ - - /* If there is no connect rule preceding all send / expect rules, an - * implicit one is inserted before all others. - */ - chk = get_first_tcpcheck_rule(&px->tcpcheck_rules); - if (!chk || chk->action != TCPCHK_ACT_CONNECT) { - chk = calloc(1, sizeof(*chk)); - if (!chk) { - ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule " - "(out of memory).\n", px->id); - ret |= ERR_ALERT | ERR_FATAL; - goto out; - } - chk->action = TCPCHK_ACT_CONNECT; - chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT); - LIST_ADD(px->tcpcheck_rules.list, &chk->list); - } - - /* Remove all comment rules. To do so, when a such rule is found, the - * comment is assigned to the following rule(s). - */ - list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) { - if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) { - free(comment); - comment = NULL; - } - - prev_action = chk->action; - switch (chk->action) { - case TCPCHK_ACT_COMMENT: - free(comment); - comment = chk->comment; - LIST_DEL(&chk->list); - free(chk); - break; - case TCPCHK_ACT_CONNECT: - if (!chk->comment && comment) - chk->comment = strdup(comment); - /* fall though */ - case TCPCHK_ACT_ACTION_KW: - free(comment); - comment = NULL; - break; - case TCPCHK_ACT_SEND: - case TCPCHK_ACT_EXPECT: - if (!chk->comment && comment) - chk->comment = strdup(comment); - break; - } - } - free(comment); - comment = NULL; - - out: - return ret; -} - -void deinit_proxy_tcpcheck(struct proxy *px) -{ - free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars); - px->tcpcheck_rules.flags = 0; - px->tcpcheck_rules.list = NULL; -} - static void deinit_srv_check(struct server *srv) { if (srv->check.state & CHK_ST_CONFIGURED) @@ -5510,110 +1970,12 @@ static void deinit_srv_agent_check(struct server *srv) srv->do_agent = 0; } -static void deinit_tcpchecks() -{ - struct tcpcheck_ruleset *rs; - struct tcpcheck_rule *r, *rb; - struct ebpt_node *node, *next; - - node = ebpt_first(&shared_tcpchecks); - while (node) { - next = ebpt_next(node); - ebpt_delete(node); - free(node->key); - rs = container_of(node, typeof(*rs), node); - list_for_each_entry_safe(r, rb, &rs->rules, list) { - LIST_DEL(&r->list); - free_tcpcheck(r, 0); - } - free(rs); - node = next; - } -} - -int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str) -{ - struct tcpcheck_rule *tcpcheck, *prev_check; - struct tcpcheck_expect *expect; - - if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL) - return 0; - memset(tcpcheck, 0, sizeof(*tcpcheck)); - tcpcheck->action = TCPCHK_ACT_EXPECT; - - expect = &tcpcheck->expect; - expect->type = TCPCHK_EXPECT_STRING; - LIST_INIT(&expect->onerror_fmt); - LIST_INIT(&expect->onsuccess_fmt); - expect->ok_status = HCHK_STATUS_L7OKD; - expect->err_status = HCHK_STATUS_L7RSP; - expect->tout_status = HCHK_STATUS_L7TOUT; - expect->data = ist2(strdup(str), strlen(str)); - if (!isttest(expect->data)) { - pool_free(pool_head_tcpcheck_rule, tcpcheck); - return 0; - } - - /* All tcp-check expect points back to the first inverse expect rule - * in a chain of one or more expect rule, potentially itself. - */ - tcpcheck->expect.head = tcpcheck; - list_for_each_entry_rev(prev_check, rules->list, list) { - if (prev_check->action == TCPCHK_ACT_EXPECT) { - if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV) - tcpcheck->expect.head = prev_check; - continue; - } - if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW) - break; - } - LIST_ADDQ(rules->list, &tcpcheck->list); - return 1; -} - -int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs) -{ - struct tcpcheck_rule *tcpcheck; - struct tcpcheck_send *send; - const char *in; - char *dst; - int i; - - if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL) - return 0; - memset(tcpcheck, 0, sizeof(*tcpcheck)); - tcpcheck->action = TCPCHK_ACT_SEND; - - send = &tcpcheck->send; - send->type = TCPCHK_SEND_STRING; - - for (i = 0; strs[i]; i++) - send->data.len += strlen(strs[i]); - - send->data.ptr = malloc(istlen(send->data) + 1); - if (!isttest(send->data)) { - pool_free(pool_head_tcpcheck_rule, tcpcheck); - return 0; - } - - dst = istptr(send->data); - for (i = 0; strs[i]; i++) - for (in = strs[i]; (*dst = *in++); dst++); - *dst = 0; - - LIST_ADDQ(rules->list, &tcpcheck->list); - return 1; -} - REGISTER_POST_SERVER_CHECK(init_srv_check); REGISTER_POST_SERVER_CHECK(init_srv_agent_check); -REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck); REGISTER_POST_CHECK(start_checks); REGISTER_SERVER_DEINIT(deinit_srv_check); REGISTER_SERVER_DEINIT(deinit_srv_agent_check); -REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck); -REGISTER_POST_DEINIT(deinit_tcpchecks); /**************************************************************************/ @@ -5630,90 +1992,6 @@ INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws); /**************************************************************************/ /************************ Check's parsing functions ***********************/ /**************************************************************************/ -/* Parses the "tcp-check" proxy keyword */ -static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx, - struct proxy *defpx, const char *file, int line, - char **errmsg) -{ - 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; - - /* Deduce the ruleset name from the proxy info */ - chunk_printf(&trash, "*tcp-check-%s_%s-%d", - ((curpx == defpx) ? "defaults" : curpx->id), - curpx->conf.file, curpx->conf.line); - - rs = find_tcpcheck_ruleset(b_orig(&trash)); - if (rs == NULL) { - rs = create_tcpcheck_ruleset(b_orig(&trash)); - if (rs == NULL) { - memprintf(errmsg, "out of memory.\n"); - goto error; - } - } - - index = 0; - if (!LIST_ISEMPTY(&rs->rules)) { - chk = LIST_PREV(&rs->rules, typeof(chk), list); - index = chk->index + 1; - } - - cur_arg = 1; - 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 || strcmp(args[cur_arg], "send-binary") == 0 || - strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 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, 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 { - struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]); - - if (!kw) { - action_kw_tcp_check_build_list(&trash); - memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'" - "%s%s. but got '%s'", - args[0], (*trash.area ? ", " : ""), trash.area, args[1]); - goto error; - } - 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 = (ret || (*errmsg != NULL)); /* Handle warning */ - - /* No error: add the tcp-check rule in the list */ - chk->index = index; - LIST_ADDQ(&rs->rules, &chk->list); - - if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && - (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) { - /* Use this ruleset if the proxy already has tcp-check enabled */ - curpx->tcpcheck_rules.list = &rs->rules; - curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS; - } - else { - /* mark this ruleset as unused for now */ - curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS; - } - - return ret; - - error: - free_tcpcheck(chk, 0); - free_tcpcheck_ruleset(rs); - return -1; -} - /* Parses the "http-check" proxy keyword */ static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx, struct proxy *defpx, const char *file, int line, @@ -7525,7 +3803,6 @@ static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, } static struct cfg_kw_list cfg_kws = {ILH, { - { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck }, { CFG_LISTEN, "http-check", proxy_parse_httpcheck }, { CFG_LISTEN, "external-check", proxy_parse_extcheck }, { 0, NULL, NULL }, diff --git a/src/mailers.c b/src/mailers.c index 39818ca0b..16ee60a28 100644 --- a/src/mailers.c +++ b/src/mailers.c @@ -26,6 +26,7 @@ #include #include #include +#include struct mailers *mailers = NULL; diff --git a/src/server.c b/src/server.c index b2c2853fc..3c83b638b 100644 --- a/src/server.c +++ b/src/server.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include diff --git a/src/tcpcheck.c b/src/tcpcheck.c new file mode 100644 index 000000000..ec1feded2 --- /dev/null +++ b/src/tcpcheck.c @@ -0,0 +1,3780 @@ +/* + * Health-checks functions. + * + * Copyright 2000-2009,2020 Willy Tarreau + * Copyright 2007-2010 Krzysztof Piotr Oledzki + * Copyright 2013 Baptiste Assmann + * Copyright 2020 Gaetan Rivet + * Copyright 2020 Christopher Faulet + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Global tree to share all tcp-checks */ +struct eb_root shared_tcpchecks = EB_ROOT; + + +DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule)); + +/**************************************************************************/ +/*************** Init/deinit tcp-check rules and ruleset ******************/ +/**************************************************************************/ +/* Releases memory allocated for a log-format string */ +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); + } +} + +/* Releases memory allocated for an HTTP header used in a tcp-check send rule */ +void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr) +{ + if (!hdr) + return; + + free_tcpcheck_fmt(&hdr->value); + istfree(&hdr->name); + free(hdr); +} + +/* Releases memory allocated for an HTTP header list used in a tcp-check send + * rule + */ +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); + } +} + +/* Releases memory allocated for a tcp-check. If in_pool is set, it means the + * tcp-check was allocated using a memory pool (it is used to instantiate email + * alerts). + */ +void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) +{ + if (!rule) + return; + + free(rule->comment); + switch (rule->action) { + case TCPCHK_ACT_SEND: + switch (rule->send.type) { + case TCPCHK_SEND_STRING: + case TCPCHK_SEND_BINARY: + istfree(&rule->send.data); + break; + case TCPCHK_SEND_STRING_LF: + case TCPCHK_SEND_BINARY_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)) + istfree(&rule->send.http.uri); + else + free_tcpcheck_fmt(&rule->send.http.uri_fmt); + istfree(&rule->send.http.vsn); + free_tcpcheck_http_hdrs(&rule->send.http.hdrs); + if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT)) + istfree(&rule->send.http.body); + else + free_tcpcheck_fmt(&rule->send.http.body_fmt); + break; + case TCPCHK_SEND_UNDEF: + break; + } + break; + case TCPCHK_ACT_EXPECT: + 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_HTTP_STATUS: + free(rule->expect.codes.codes); + break; + case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_BINARY: + case TCPCHK_EXPECT_HTTP_BODY: + istfree(&rule->expect.data); + break; + case TCPCHK_EXPECT_STRING_REGEX: + case TCPCHK_EXPECT_BINARY_REGEX: + case TCPCHK_EXPECT_HTTP_STATUS_REGEX: + case TCPCHK_EXPECT_HTTP_BODY_REGEX: + regex_free(rule->expect.regex); + break; + case TCPCHK_EXPECT_STRING_LF: + case TCPCHK_EXPECT_BINARY_LF: + case TCPCHK_EXPECT_HTTP_BODY_LF: + free_tcpcheck_fmt(&rule->expect.fmt); + break; + case TCPCHK_EXPECT_HTTP_HEADER: + if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) + regex_free(rule->expect.hdr.name_re); + else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) + free_tcpcheck_fmt(&rule->expect.hdr.name_fmt); + else + istfree(&rule->expect.hdr.name); + + if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) + regex_free(rule->expect.hdr.value_re); + else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) + free_tcpcheck_fmt(&rule->expect.hdr.value_fmt); + else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE)) + istfree(&rule->expect.hdr.value); + break; + case TCPCHK_EXPECT_CUSTOM: + case TCPCHK_EXPECT_UNDEF: + break; + } + break; + case TCPCHK_ACT_CONNECT: + free(rule->connect.sni); + free(rule->connect.alpn); + release_sample_expr(rule->connect.port_expr); + break; + case TCPCHK_ACT_COMMENT: + break; + case TCPCHK_ACT_ACTION_KW: + free(rule->action_kw.rule); + break; + } + + if (in_pool) + pool_free(pool_head_tcpcheck_rule, rule); + else + free(rule); +} + +/* Creates a tcp-check variable used in preset variables before executing a + * tcp-check ruleset. + */ +struct tcpcheck_var *create_tcpcheck_var(const struct ist name) +{ + struct tcpcheck_var *var = NULL; + + var = calloc(1, sizeof(*var)); + if (var == NULL) + return NULL; + + var->name = istdup(name); + if (!isttest(var->name)) { + free(var); + return NULL; + } + + LIST_INIT(&var->list); + return var; +} + +/* Releases memory allocated for a preset tcp-check variable */ +void free_tcpcheck_var(struct tcpcheck_var *var) +{ + if (!var) + return; + + istfree(&var->name); + if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) + free(var->data.u.str.area); + else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) + free(var->data.u.meth.str.area); + free(var); +} + +/* Releases a list of preset tcp-check variables */ +void free_tcpcheck_vars(struct list *vars) +{ + struct tcpcheck_var *var, *back; + + list_for_each_entry_safe(var, back, vars, list) { + LIST_DEL(&var->list); + free_tcpcheck_var(var); + } +} + +/* Duplicate a list of preset tcp-check variables */ +int dup_tcpcheck_vars(struct list *dst, struct list *src) +{ + struct tcpcheck_var *var, *new = NULL; + + list_for_each_entry(var, src, list) { + new = create_tcpcheck_var(var->name); + if (!new) + goto error; + new->data.type = var->data.type; + if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) { + if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL) + goto error; + if (var->data.type == SMP_T_STR) + new->data.u.str.area[new->data.u.str.data] = 0; + } + else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) { + if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL) + goto error; + new->data.u.str.area[new->data.u.str.data] = 0; + new->data.u.meth.meth = var->data.u.meth.meth; + } + else + new->data.u = var->data.u; + LIST_ADDQ(dst, &new->list); + } + return 1; + + error: + free(new); + return 0; +} + +/* Looks for a shared tcp-check ruleset given its name. */ +struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name) +{ + struct tcpcheck_ruleset *rs; + struct ebpt_node *node; + + node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name)); + if (node) { + rs = container_of(node, typeof(*rs), node); + return rs; + } + return NULL; +} + +/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks + * tree. + */ +struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name) +{ + struct tcpcheck_ruleset *rs; + + rs = calloc(1, sizeof(*rs)); + if (rs == NULL) + return NULL; + + rs->node.key = strdup(name); + if (rs->node.key == NULL) { + free(rs); + return NULL; + } + + LIST_INIT(&rs->rules); + ebis_insert(&shared_tcpchecks, &rs->node); + return rs; +} + +/* Releases memory allocated by a tcp-check ruleset. */ +void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs) +{ + struct tcpcheck_rule *r, *rb; + + if (!rs) + return; + + ebpt_delete(&rs->node); + free(rs->node.key); + list_for_each_entry_safe(r, rb, &rs->rules, list) { + LIST_DEL(&r->list); + free_tcpcheck(r, 0); + } + free(rs); +} + + +/**************************************************************************/ +/**************** Everything about tcp-checks execution *******************/ +/**************************************************************************/ +/* Returns the id of a step in a tcp-check ruleset */ +int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule) +{ + if (!rule) + rule = check->current_step; + + /* no last started step => first step */ + if (!rule) + return 1; + + /* last step is the first implicit connect */ + if (rule->index == 0 && + rule->action == TCPCHK_ACT_CONNECT && + (rule->connect.options & TCPCHK_OPT_IMPLICIT)) + return 0; + + return rule->index + 1; +} + +/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list or + * NULL if none was found. + */ +struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules) +{ + struct tcpcheck_rule *r; + + list_for_each_entry(r, rules->list, list) { + if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) + return r; + } + return NULL; +} + +/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list or + * NULL if none was found. + */ +static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules) +{ + struct tcpcheck_rule *r; + + list_for_each_entry_rev(r, rules->list, list) { + if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) + return r; + } + return NULL; +} + +/* Returns the non COMMENT/ACTION_KW tcp-check rule from list following + * or NULL if non was found. If is NULL, it relies on + * get_first_tcpcheck_rule(). + */ +static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start) +{ + struct tcpcheck_rule *r; + + if (!start) + return get_first_tcpcheck_rule(rules); + + r = LIST_NEXT(&start->list, typeof(r), list); + list_for_each_entry_from(r, rules->list, list) { + if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW) + return r; + } + return NULL; +} + + +/* Creates info message when a tcp-check healthcheck fails on an expect rule */ +static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule, + int match, struct ist info) +{ + struct sample *smp; + + /* Follows these step to produce the info message: + * 1. if info field is already provided, copy it + * 2. if the expect rule provides an onerror log-format string, + * use it to produce the message + * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing + * 4. Otherwise produce the generic tcp-check info message + */ + if (istlen(info)) { + chunk_strncat(msg, istptr(info), istlen(info)); + goto comment; + } + else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) { + msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt); + goto comment; + } + + if (check->type == PR_O2_TCPCHK_CHK && + (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK) + goto comment; + + chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content")); + switch (rule->expect.type) { + case TCPCHK_EXPECT_HTTP_STATUS: + chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_HTTP_BODY: + chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data), + tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_BINARY: + chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_STRING_REGEX: + case TCPCHK_EXPECT_HTTP_STATUS_REGEX: + case TCPCHK_EXPECT_HTTP_BODY_REGEX: + chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_BINARY_REGEX: + chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_STRING_LF: + case TCPCHK_EXPECT_HTTP_BODY_LF: + chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_BINARY_LF: + chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_CUSTOM: + chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule)); + break; + case TCPCHK_EXPECT_HTTP_HEADER: + chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule)); + case TCPCHK_EXPECT_UNDEF: + /* Should never happen. */ + return; + } + + comment: + /* If the failing expect rule provides a comment, it is concatenated to + * the info message. + */ + if (rule->comment) { + chunk_strcat(msg, " comment: "); + chunk_strcat(msg, rule->comment); + } + + /* Finally, the check status code is set if the failing expect rule + * defines a status expression. + */ + if (rule->expect.status_expr) { + smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL, + rule->expect.status_expr, SMP_T_STR); + + if (smp && sample_casts[smp->data.type][SMP_T_SINT] && + sample_casts[smp->data.type][SMP_T_SINT](smp)) + check->code = smp->data.u.sint; + } + + *(b_tail(msg)) = '\0'; +} + +/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */ +static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule, + struct ist info) +{ + struct sample *smp; + + /* Follows these step to produce the info message: + * 1. if info field is already provided, copy it + * 2. if the expect rule provides an onsucces log-format string, + * use it to produce the message + * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing + * 4. Otherwise produce the generic tcp-check info message + */ + if (istlen(info)) + chunk_strncat(msg, istptr(info), istlen(info)); + if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt)) + msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), + &rule->expect.onsuccess_fmt); + else if (check->type == PR_O2_TCPCHK_CHK && + (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) + chunk_strcat(msg, "(tcp-check)"); + + /* Finally, the check status code is set if the expect rule defines a + * status expression. + */ + if (rule->expect.status_expr) { + smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL, + rule->expect.status_expr, SMP_T_STR); + + if (smp && sample_casts[smp->data.type][SMP_T_SINT] && + sample_casts[smp->data.type][SMP_T_SINT](smp)) + check->code = smp->data.u.sint; + } + + *(b_tail(msg)) = '\0'; +} + +/* Internal functions to parse and validate a MySQL packet in the context of an + * expect rule. It start to parse the input buffer at the offset . If + * is set, no more data are expected. + */ +static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule, + unsigned int offset, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + enum healthcheck_status status; + struct buffer *msg = NULL; + struct ist desc = IST_NULL; + unsigned int err = 0, plen = 0; + + + /* 3 Bytes for the packet length and 1 byte for the sequence id */ + if (b_data(&check->bi) < offset+4) { + if (!last_read) + goto wait_more_data; + + /* invalid length or truncated response */ + status = HCHK_STATUS_L7RSP; + goto error; + } + + plen = ((unsigned char) *b_peek(&check->bi, offset)) + + (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) + + (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16); + + if (b_data(&check->bi) < offset+plen+4) { + if (!last_read) + goto wait_more_data; + + /* invalid length or truncated response */ + status = HCHK_STATUS_L7RSP; + goto error; + } + + if (*b_peek(&check->bi, offset+4) == '\xff') { + /* MySQL Error packet always begin with field_count = 0xff */ + status = HCHK_STATUS_L7STS; + err = ((unsigned char) *b_peek(&check->bi, offset+5)) + + (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8); + desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7); + goto error; + } + + if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) { + /* Not the last rule, continue */ + goto out; + } + + /* We set the MySQL Version in description for information purpose + * FIXME : it can be cool to use MySQL Version for other purpose, + * like mark as down old MySQL server. + */ + status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); + set_server_check_status(check, status, b_peek(&check->bi, 5)); + + out: + free_trash_chunk(msg); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + check->code = err; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_expect_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; +} + +/* Custom tcp-check expect function to parse and validate the MySQL initial + * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data, + * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an + * error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + return tcpcheck_mysql_expect_packet(check, rule, 0, last_read); +} + +/* Custom tcp-check expect function to parse and validate the MySQL OK packet + * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more + * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if + * an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + unsigned int hslen = 0; + + hslen = 4 + ((unsigned char) *b_head(&check->bi)) + + (((unsigned char) *(b_peek(&check->bi, 1))) << 8) + + (((unsigned char) *(b_peek(&check->bi, 2))) << 16); + + return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read); +} + +/* Custom tcp-check expect function to parse and validate the LDAP bind response + * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data, + * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an + * error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + enum healthcheck_status status; + struct buffer *msg = NULL; + struct ist desc = IST_NULL; + unsigned short msglen = 0; + + /* Check if the server speaks LDAP (ASN.1/BER) + * http://en.wikipedia.org/wiki/Basic_Encoding_Rules + * http://tools.ietf.org/html/rfc4511 + */ + /* size of LDAPMessage */ + msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0; + + /* http://tools.ietf.org/html/rfc4511#section-4.2.2 + * messageID: 0x02 0x01 0x01: INTEGER 1 + * protocolOp: 0x61: bindResponse + */ + if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) { + status = HCHK_STATUS_L7RSP; + desc = ist("Not LDAPv3 protocol"); + goto error; + } + + /* size of bindResponse */ + msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0; + + /* http://tools.ietf.org/html/rfc4511#section-4.1.9 + * ldapResult: 0x0a 0x01: ENUMERATION + */ + if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) { + status = HCHK_STATUS_L7RSP; + desc = ist("Not LDAPv3 protocol"); + goto error; + } + + /* http://tools.ietf.org/html/rfc4511#section-4.1.9 + * resultCode + */ + check->code = *(b_head(&check->bi) + msglen + 9); + if (check->code) { + status = HCHK_STATUS_L7STS; + desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9"); + goto error; + } + + status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); + set_server_check_status(check, status, "Success"); + + out: + free_trash_chunk(msg); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_expect_onerror_message(msg, check, rule, 0, desc); + set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); + goto out; +} + +/* Custom tcp-check expect function to parse and validate the SPOP hello agent + * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE + * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + enum healthcheck_status status; + struct buffer *msg = NULL; + struct ist desc = IST_NULL; + unsigned int framesz; + + + memcpy(&framesz, b_head(&check->bi), 4); + framesz = ntohl(framesz); + + if (!last_read && b_data(&check->bi) < (4+framesz)) + goto wait_more_data; + + memset(b_orig(&trash), 0, b_size(&trash)); + if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) { + status = HCHK_STATUS_L7RSP; + desc = ist2(b_orig(&trash), strlen(b_orig(&trash))); + goto error; + } + + status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); + set_server_check_status(check, status, "SPOA server is ok"); + + out: + free_trash_chunk(msg); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_expect_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; +} + +/* Custom tcp-check expect function to parse and validate the agent-check + * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE + * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP; + enum healthcheck_status status = HCHK_STATUS_CHECKED; + const char *hs = NULL; /* health status */ + const char *as = NULL; /* admin status */ + const char *ps = NULL; /* performance status */ + const char *cs = NULL; /* maxconn */ + const char *err = NULL; /* first error to report */ + const char *wrn = NULL; /* first warning to report */ + char *cmd, *p; + + /* We're getting an agent check response. The agent could + * have been disabled in the mean time with a long check + * still pending. It is important that we ignore the whole + * response. + */ + if (!(check->state & CHK_ST_ENABLED)) + goto out; + + /* The agent supports strings made of a single line ended by the + * first CR ('\r') or LF ('\n'). This line is composed of words + * delimited by spaces (' '), tabs ('\t'), or commas (','). The + * line may optionally contained a description of a state change + * after a sharp ('#'), which is only considered if a health state + * is announced. + * + * Words may be composed of : + * - a numeric weight suffixed by the percent character ('%'). + * - a health status among "up", "down", "stopped", and "fail". + * - an admin status among "ready", "drain", "maint". + * + * These words may appear in any order. If multiple words of the + * same category appear, the last one wins. + */ + + p = b_head(&check->bi); + while (*p && *p != '\n' && *p != '\r') + p++; + + if (!*p) { + if (!last_read) + goto wait_more_data; + + /* at least inform the admin that the agent is mis-behaving */ + set_server_check_status(check, check->status, "Ignoring incomplete line from agent"); + goto out; + } + + *p = 0; + cmd = b_head(&check->bi); + + while (*cmd) { + /* look for next word */ + if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') { + cmd++; + continue; + } + + if (*cmd == '#') { + /* this is the beginning of a health status description, + * skip the sharp and blanks. + */ + cmd++; + while (*cmd == '\t' || *cmd == ' ') + cmd++; + break; + } + + /* find the end of the word so that we have a null-terminated + * word between and

. + */ + p = cmd + 1; + while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',') + p++; + if (*p) + *p++ = 0; + + /* first, health statuses */ + if (strcasecmp(cmd, "up") == 0) { + check->server->check.health = check->server->check.rise + check->server->check.fall - 1; + status = HCHK_STATUS_L7OKD; + hs = cmd; + } + else if (strcasecmp(cmd, "down") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + else if (strcasecmp(cmd, "stopped") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + else if (strcasecmp(cmd, "fail") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + /* admin statuses */ + else if (strcasecmp(cmd, "ready") == 0) { + as = cmd; + } + else if (strcasecmp(cmd, "drain") == 0) { + as = cmd; + } + else if (strcasecmp(cmd, "maint") == 0) { + as = cmd; + } + /* try to parse a weight here and keep the last one */ + else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) { + ps = cmd; + } + /* try to parse a maxconn here */ + else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) { + cs = cmd; + } + else { + /* keep a copy of the first error */ + if (!err) + err = cmd; + } + /* skip to next word */ + cmd = p; + } + /* here, cmd points either to \0 or to the beginning of a + * description. Skip possible leading spaces. + */ + while (*cmd == ' ' || *cmd == '\n') + cmd++; + + /* First, update the admin status so that we avoid sending other + * possibly useless warnings and can also update the health if + * present after going back up. + */ + if (as) { + if (strcasecmp(as, "drain") == 0) + srv_adm_set_drain(check->server); + else if (strcasecmp(as, "maint") == 0) + srv_adm_set_maint(check->server); + else + srv_adm_set_ready(check->server); + } + + /* now change weights */ + if (ps) { + const char *msg; + + msg = server_parse_weight_change_request(check->server, ps); + if (!wrn || !*wrn) + wrn = msg; + } + + if (cs) { + const char *msg; + + cs += strlen("maxconn:"); + + msg = server_parse_maxconn_change_request(check->server, cs); + if (!wrn || !*wrn) + wrn = msg; + } + + /* and finally health status */ + if (hs) { + /* We'll report some of the warnings and errors we have + * here. Down reports are critical, we leave them untouched. + * Lack of report, or report of 'UP' leaves the room for + * ERR first, then WARN. + */ + const char *msg = cmd; + struct buffer *t; + + if (!*msg || status == HCHK_STATUS_L7OKD) { + if (err && *err) + msg = err; + else if (wrn && *wrn) + msg = wrn; + } + + t = get_trash_chunk(); + chunk_printf(t, "via agent : %s%s%s%s", + hs, *msg ? " (" : "", + msg, *msg ? ")" : ""); + set_server_check_status(check, status, t->area); + } + else if (err && *err) { + /* No status change but we'd like to report something odd. + * Just report the current state and copy the message. + */ + chunk_printf(&trash, "agent reports an error : %s", err); + set_server_check_status(check, status/*check->status*/, trash.area); + } + else if (wrn && *wrn) { + /* No status change but we'd like to report something odd. + * Just report the current state and copy the message. + */ + chunk_printf(&trash, "agent warns : %s", wrn); + set_server_check_status(check, status/*check->status*/, trash.area); + } + else + set_server_check_status(check, status, NULL); + + out: + return ret; + + wait_more_data: + ret = TCPCHK_EVAL_WAIT; + goto out; +} + +/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the + * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or + * TCPCHK_EVAL_STOP if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + struct tcpcheck_connect *connect = &rule->connect; + struct proxy *proxy = check->proxy; + struct server *s = check->server; + struct task *t = check->task; + struct conn_stream *cs; + struct connection *conn = NULL; + struct protocol *proto; + struct xprt_ops *xprt; + struct tcpcheck_rule *next; + int status, port; + + /* For a connect action we'll create a new connection. We may also have + * to kill a previous one. But we don't want to leave *without* a + * connection if we came here from the connection layer, hence with a + * connection. Thus we'll proceed in the following order : + * 1: close but not release previous connection (handled by the caller) + * 2: try to get a new connection + * 3: release and replace the old one on success + */ + + /* 2- prepare new connection */ + cs = cs_new(NULL); + if (!cs) { + chunk_printf(&trash, "TCPCHK error allocating connection at step %d", + tcpcheck_get_step_id(check, rule)); + if (rule->comment) + chunk_appendf(&trash, " comment: '%s'", rule->comment); + set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area); + ret = TCPCHK_EVAL_STOP; + goto out; + } + + /* 3- release and replace the old one on success */ + if (check->cs) { + if (check->wait_list.events) + check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events, + &check->wait_list); + + /* We may have been scheduled to run, and the I/O handler + * expects to have a cs, so remove the tasklet + */ + tasklet_remove_from_tasklet_list(check->wait_list.tasklet); + cs_destroy(check->cs); + } + + tasklet_set_tid(check->wait_list.tasklet, tid); + + check->cs = cs; + conn = cs->conn; + conn_set_owner(conn, check->sess, NULL); + + /* Maybe there were an older connection we were waiting on */ + check->wait_list.events = 0; + conn->target = s ? &s->obj_type : &proxy->obj_type; + + /* no client address */ + if (!sockaddr_alloc(&conn->dst)) { + status = SF_ERR_RESOURCE; + goto fail_check; + } + + /* connect to the connect rule addr if specified, otherwise the check + * addr if specified on the server. otherwise, use the server addr (it + * MUST exist at this step). + */ + *conn->dst = (is_addr(&connect->addr) + ? connect->addr + : (is_addr(&check->addr) ? check->addr : s->addr)); + proto = protocol_by_family(conn->dst->ss_family); + + port = 0; + if (!port && connect->port) + port = connect->port; + if (!port && connect->port_expr) { + struct sample *smp; + + smp = sample_fetch_as_type(check->proxy, check->sess, NULL, + SMP_OPT_DIR_REQ | SMP_OPT_FINAL, + connect->port_expr, SMP_T_SINT); + if (smp) + port = smp->data.u.sint; + } + if (!port && is_inet_addr(&connect->addr)) + port = get_host_port(&connect->addr); + if (!port && check->port) + port = check->port; + if (!port && is_inet_addr(&check->addr)) + port = get_host_port(&check->addr); + if (!port) { + /* The server MUST exist here */ + port = s->svc_port; + } + set_host_port(conn->dst, port); + + xprt = ((connect->options & TCPCHK_OPT_SSL) + ? xprt_get(XPRT_SSL) + : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW))); + + conn_prepare(conn, proto, xprt); + cs_attach(cs, check, &check_conn_cb); + + status = SF_ERR_INTERNAL; + next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule); + if (proto && proto->connect) { + int flags = 0; + + if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) + flags |= CONNECT_HAS_DATA; + if (!next || next->action != TCPCHK_ACT_EXPECT) + flags |= CONNECT_DELACK_ALWAYS; + status = proto->connect(conn, flags); + } + + if (status != SF_ERR_NONE) + goto fail_check; + + conn->flags |= CO_FL_PRIVATE; + conn->ctx = cs; + + /* The mux may be initialized now if there isn't server attached to the + * check (email alerts) or if there is a mux proto specified or if there + * is no alpn. + */ + if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) || + connect->mux_proto || (!connect->alpn && !check->alpn_str)) { + const struct mux_ops *mux_ops; + + if (connect->mux_proto) + mux_ops = connect->mux_proto->mux; + else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) + mux_ops = check->mux_proto->mux; + else { + int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK + ? PROTO_MODE_HTTP + : PROTO_MODE_TCP); + + mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode); + } + if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) { + status = SF_ERR_INTERNAL; + goto fail_check; + } + } + +#ifdef USE_OPENSSL + if (connect->sni) + ssl_sock_set_servername(conn, connect->sni); + else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni) + ssl_sock_set_servername(conn, s->check.sni); + + if (connect->alpn) + ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len); + else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str) + ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len); +#endif + if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) { + conn->send_proxy_ofs = 1; + conn->flags |= CO_FL_SOCKS4; + } + else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) { + conn->send_proxy_ofs = 1; + conn->flags |= CO_FL_SOCKS4; + } + + if (connect->options & TCPCHK_OPT_SEND_PROXY) { + conn->send_proxy_ofs = 1; + conn->flags |= CO_FL_SEND_PROXY; + } + else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) { + conn->send_proxy_ofs = 1; + conn->flags |= CO_FL_SEND_PROXY; + } + + if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) { + /* Some servers don't like reset on close */ + fdtab[cs->conn->handle.fd].linger_risk = 0; + } + + if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) { + if (xprt_add_hs(conn) < 0) + status = SF_ERR_RESOURCE; + } + + fail_check: + /* It can return one of : + * - SF_ERR_NONE if everything's OK + * - SF_ERR_SRVTO if there are no more servers + * - SF_ERR_SRVCL if the connection was refused by the server + * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) + * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) + * - SF_ERR_INTERNAL for any other purely internal errors + * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted. + * Note that we try to prevent the network stack from sending the ACK during the + * connect() when a pure TCP check is used (without PROXY protocol). + */ + switch (status) { + case SF_ERR_NONE: + /* 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 + */ + t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter)); + + if (proxy->timeout.check && proxy->timeout.connect) { + int t_con = tick_add(now_ms, proxy->timeout.connect); + t->expire = tick_first(t->expire, t_con); + } + break; + case SF_ERR_SRVTO: /* ETIMEDOUT */ + case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */ + case SF_ERR_PRXCOND: + case SF_ERR_RESOURCE: + case SF_ERR_INTERNAL: + chk_report_conn_err(check, errno, 0); + ret = TCPCHK_EVAL_STOP; + goto out; + } + + /* don't do anything until the connection is established */ + if (conn->flags & CO_FL_WAIT_XPRT) { + if (conn->mux) { + if (next && next->action == TCPCHK_ACT_SEND) + conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); + else + conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); + } + ret = TCPCHK_EVAL_WAIT; + goto out; + } + + out: + if (conn && check->result == CHK_RES_FAILED) + conn->flags |= CO_FL_ERROR; + return ret; +} + +/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data + * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or + * TCPCHK_EVAL_STOP if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + struct tcpcheck_send *send = &rule->send; + struct conn_stream *cs = check->cs; + struct connection *conn = cs_conn(cs); + struct buffer *tmp = NULL; + struct htx *htx = NULL; + + /* reset the read & write buffer */ + b_reset(&check->bi); + b_reset(&check->bo); + + switch (send->type) { + case TCPCHK_SEND_STRING: + case TCPCHK_SEND_BINARY: + if (istlen(send->data) >= b_size(&check->bo)) { + chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d", + (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo), + tcpcheck_get_step_id(check, rule)); + set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); + ret = TCPCHK_EVAL_STOP; + goto out; + } + b_putist(&check->bo, send->data); + break; + case TCPCHK_SEND_STRING_LF: + check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt); + if (!b_data(&check->bo)) + goto out; + break; + case TCPCHK_SEND_BINARY_LF: { + int len = b_size(&check->bo); + + tmp = alloc_trash_chunk(); + if (!tmp) + goto error_lf; + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt); + if (!b_data(tmp)) + goto out; + tmp->area[tmp->data] = '\0'; + if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0) + goto error_lf; + check->bo.data = len; + break; + } + case TCPCHK_SEND_HTTP: { + struct htx_sl *sl; + struct ist meth, uri, vsn, clen, body; + unsigned int slflags = 0; + + tmp = alloc_trash_chunk(); + if (!tmp) + goto error_htx; + + 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]); + if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) { + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt); + uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/")); + } + else + uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); + vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0")); + + if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') || + (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))) + slflags |= HTX_SL_F_VER_11; + slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN); + if (!isttest(send->http.body)) + slflags |= HTX_SL_F_BODYLESS; + + htx = htx_from_buf(&check->bo); + sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn); + if (!sl) + goto error_htx; + sl->info.req.meth = send->http.meth.meth; + if (!http_update_host(htx, sl, uri)) + goto error_htx; + + if (!LIST_ISEMPTY(&send->http.hdrs)) { + struct tcpcheck_http_hdr *hdr; + struct ist hdr_value; + + 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; + hdr_value = ist2(b_orig(tmp), b_data(tmp)); + if (!htx_add_header(htx, hdr->name, hdr_value)) + goto error_htx; + if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) { + if (!http_update_authority(htx, sl, hdr_value)) + goto error_htx; + } + } + + } + if (check->proxy->options2 & PR_O2_CHK_SNDST) { + chunk_reset(tmp); + httpchk_build_status_header(check->server, tmp); + if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp)))) + goto error_htx; + } + + + if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) { + chunk_reset(tmp); + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt); + body = ist2(b_orig(tmp), b_data(tmp)); + } + else + body = send->http.body; + clen = ist((!istlen(body) ? "0" : ultoa(istlen(body)))); + + if (!htx_add_header(htx, ist("Connection"), ist("close")) || + !htx_add_header(htx, ist("Content-length"), clen)) + goto error_htx; + + + if (!htx_add_endof(htx, HTX_BLK_EOH) || + (istlen(body) && !htx_add_data_atonce(htx, body)) || + !htx_add_endof(htx, HTX_BLK_EOM)) + goto error_htx; + + htx_to_buf(htx, &check->bo); + break; + } + case TCPCHK_SEND_UNDEF: + /* Should never happen. */ + ret = TCPCHK_EVAL_STOP; + goto out; + }; + + + if (conn->mux->snd_buf(cs, &check->bo, + (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) { + if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) { + ret = TCPCHK_EVAL_STOP; + goto out; + } + } + if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) { + cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); + ret = TCPCHK_EVAL_WAIT; + goto out; + } + + out: + free_trash_chunk(tmp); + return ret; + + error_htx: + if (htx) { + htx_reset(htx); + htx_to_buf(htx, &check->bo); + } + chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d", + tcpcheck_get_step_id(check, rule)); + set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); + ret = TCPCHK_EVAL_STOP; + goto out; + + error_lf: + chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d", + tcpcheck_get_step_id(check, rule)); + set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); + ret = TCPCHK_EVAL_STOP; + goto out; + +} + +/* Try to receive data before evaluating a tcp-check expect rule. Returns + * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing + * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or + * TCPCHK_EVAL_STOP if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule) +{ + struct conn_stream *cs = check->cs; + struct connection *conn = cs_conn(cs); + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + size_t max, read, cur_read = 0; + int is_empty; + int read_poll = MAX_READ_POLL_LOOPS; + + if (check->wait_list.events & SUB_RETRY_RECV) + goto wait_more_data; + + if (cs->flags & CS_FL_EOS) + goto end_recv; + + /* errors on the connection and the conn-stream were already checked */ + + /* prepare to detect if the mux needs more room */ + cs->flags &= ~CS_FL_WANT_ROOM; + + while ((cs->flags & CS_FL_RCV_MORE) || + (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) { + max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi)); + read = conn->mux->rcv_buf(cs, &check->bi, max, 0); + cur_read += read; + if (!read || + (cs->flags & CS_FL_WANT_ROOM) || + (--read_poll <= 0) || + (read < max && read >= global.tune.recv_enough)) + break; + } + + end_recv: + is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi)); + if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) { + /* 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. + */ + goto stop; + } + if (!cur_read) { + if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) { + conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); + goto wait_more_data; + } + if (is_empty) { + int status; + + chunk_printf(&trash, "TCPCHK got an empty response at step %d", + tcpcheck_get_step_id(check, rule)); + if (rule->comment) + chunk_appendf(&trash, " comment: '%s'", rule->comment); + + status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP); + set_server_check_status(check, status, trash.area); + goto stop; + } + } + + out: + return ret; + + stop: + ret = TCPCHK_EVAL_STOP; + goto out; + + wait_more_data: + ret = TCPCHK_EVAL_WAIT; + goto out; +} + +/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If is set , no more data + * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data, + * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an + * error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + struct htx *htx = htxbuf(&check->bi); + struct htx_sl *sl; + struct htx_blk *blk; + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + struct tcpcheck_expect *expect = &rule->expect; + struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL; + enum healthcheck_status status = HCHK_STATUS_L7RSP; + struct ist desc = IST_NULL; + int i, match, inverse; + + last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM)); + + if (htx->flags & HTX_FL_PARSING_ERROR) { + status = HCHK_STATUS_L7RSP; + goto error; + } + + if (htx_is_empty(htx)) { + if (last_read) { + status = HCHK_STATUS_L7RSP; + goto error; + } + goto wait_more_data; + } + + sl = http_get_stline(htx); + check->code = sl->info.res.status; + + 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; + status = expect->err_status; + + switch (expect->type) { + case TCPCHK_EXPECT_HTTP_STATUS: + match = 0; + for (i = 0; i < expect->codes.num; i++) { + if (sl->info.res.status >= expect->codes.codes[i][0] && + sl->info.res.status <= expect->codes.codes[i][1]) { + match = 1; + break; + } + } + + /* Set status and description in case of error */ + status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); + if (LIST_ISEMPTY(&expect->onerror_fmt)) + desc = htx_sl_res_reason(sl); + break; + case TCPCHK_EXPECT_HTTP_STATUS_REGEX: + match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); + + /* Set status and description in case of error */ + status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); + if (LIST_ISEMPTY(&expect->onerror_fmt)) + desc = htx_sl_res_reason(sl); + break; + + case TCPCHK_EXPECT_HTTP_HEADER: { + struct http_hdr_ctx ctx; + struct ist npat, vpat, value; + int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL)); + + if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { + nbuf = alloc_trash_chunk(); + if (!nbuf) { + status = HCHK_STATUS_L7RSP; + desc = ist("Failed to allocate buffer to eval log-format string"); + goto error; + } + nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt); + if (!b_data(nbuf)) { + status = HCHK_STATUS_L7RSP; + desc = ist("log-format string evaluated to an empty string"); + goto error; + } + npat = ist2(b_orig(nbuf), b_data(nbuf)); + } + else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)) + npat = expect->hdr.name; + + if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { + vbuf = alloc_trash_chunk(); + if (!vbuf) { + status = HCHK_STATUS_L7RSP; + desc = ist("Failed to allocate buffer to eval log-format string"); + goto error; + } + vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt); + if (!b_data(vbuf)) { + status = HCHK_STATUS_L7RSP; + desc = ist("log-format string evaluated to an empty string"); + goto error; + } + vpat = ist2(b_orig(vbuf), b_data(vbuf)); + } + else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)) + vpat = expect->hdr.value; + + match = 0; + ctx.blk = NULL; + while (1) { + switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) { + case TCPCHK_EXPT_FL_HTTP_HNAME_STR: + if (!http_find_str_header(htx, npat, &ctx, full)) + goto end_of_match; + break; + case TCPCHK_EXPT_FL_HTTP_HNAME_BEG: + if (!http_find_pfx_header(htx, npat, &ctx, full)) + goto end_of_match; + break; + case TCPCHK_EXPT_FL_HTTP_HNAME_END: + if (!http_find_sfx_header(htx, npat, &ctx, full)) + goto end_of_match; + break; + case TCPCHK_EXPT_FL_HTTP_HNAME_SUB: + if (!http_find_sub_header(htx, npat, &ctx, full)) + goto end_of_match; + break; + case TCPCHK_EXPT_FL_HTTP_HNAME_REG: + if (!http_match_header(htx, expect->hdr.name_re, &ctx, full)) + goto end_of_match; + break; + default: + /* should never happen */ + goto end_of_match; + } + + /* A header has matched the name pattern, let's test its + * value now (always defined from there). If there is no + * value pattern, it is a good match. + */ + + if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) { + match = 1; + goto end_of_match; + } + + value = ctx.value; + switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) { + case TCPCHK_EXPT_FL_HTTP_HVAL_STR: + if (isteq(value, vpat)) { + match = 1; + goto end_of_match; + } + break; + case TCPCHK_EXPT_FL_HTTP_HVAL_BEG: + if (istlen(value) < istlen(vpat)) + break; + value = ist2(istptr(value), istlen(vpat)); + if (isteq(value, vpat)) { + match = 1; + goto end_of_match; + } + break; + case TCPCHK_EXPT_FL_HTTP_HVAL_END: + if (istlen(value) < istlen(vpat)) + break; + value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat)); + if (isteq(value, vpat)) { + match = 1; + goto end_of_match; + } + break; + case TCPCHK_EXPT_FL_HTTP_HVAL_SUB: + if (isttest(istist(value, vpat))) { + match = 1; + goto end_of_match; + } + break; + case TCPCHK_EXPT_FL_HTTP_HVAL_REG: + if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) { + match = 1; + goto end_of_match; + } + break; + } + } + + end_of_match: + status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS); + if (LIST_ISEMPTY(&expect->onerror_fmt)) + desc = htx_sl_res_reason(sl); + break; + } + + case TCPCHK_EXPECT_HTTP_BODY: + case TCPCHK_EXPECT_HTTP_BODY_REGEX: + case TCPCHK_EXPECT_HTTP_BODY_LF: + match = 0; + chunk_reset(&trash); + for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) { + enum htx_blk_type type = htx_get_blk_type(blk); + + if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT) + break; + if (type == HTX_BLK_DATA) { + if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk))) + break; + } + } + + if (!b_data(&trash)) { + if (!last_read) + goto wait_more_data; + status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP); + if (LIST_ISEMPTY(&expect->onerror_fmt)) + desc = ist("HTTP content check could not find a response body"); + goto error; + } + + if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) { + tmp = alloc_trash_chunk(); + if (!tmp) { + status = HCHK_STATUS_L7RSP; + desc = ist("Failed to allocate buffer to eval log-format string"); + goto error; + } + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt); + if (!b_data(tmp)) { + status = HCHK_STATUS_L7RSP; + desc = ist("log-format string evaluated to an empty string"); + goto error; + } + } + + if (!last_read && + ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) || + ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) || + (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + + if (expect->type ==TCPCHK_EXPECT_HTTP_BODY) + match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL; + else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF) + match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL; + else + match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash)); + + /* Set status and description in case of error */ + status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP); + if (LIST_ISEMPTY(&expect->onerror_fmt)) + desc = (inverse + ? ist("HTTP check matched unwanted content") + : ist("HTTP content check did not match")); + break; + + + default: + /* should never happen */ + status = ((status != HCHK_STATUS_UNKNOWN) ? 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(tmp); + free_trash_chunk(nbuf); + free_trash_chunk(vbuf); + free_trash_chunk(msg); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_expect_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; +} + +/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for + * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP + * if an error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_eval_expect(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, *tmp = NULL; + struct ist desc = IST_NULL; + enum healthcheck_status status; + 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. + */ + if (!last_read) { + if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) && + (b_data(&check->bi) < istlen(expect->data))) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + } + + inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV); + /* Make GCC happy ; initialize match to a failure state. */ + match = inverse; + status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP); + + switch (expect->type) { + case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_BINARY: + match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL; + break; + case TCPCHK_EXPECT_STRING_REGEX: + match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1)); + break; + + case TCPCHK_EXPECT_BINARY_REGEX: + chunk_reset(&trash); + dump_binary(&trash, b_head(&check->bi), b_data(&check->bi)); + match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1)); + break; + + case TCPCHK_EXPECT_STRING_LF: + case TCPCHK_EXPECT_BINARY_LF: + match = 0; + tmp = alloc_trash_chunk(); + if (!tmp) { + status = HCHK_STATUS_L7RSP; + desc = ist("Failed to allocate buffer to eval format string"); + goto error; + } + tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt); + if (!b_data(tmp)) { + status = HCHK_STATUS_L7RSP; + desc = ist("log-format string evaluated to an empty string"); + goto error; + } + if (expect->type == TCPCHK_EXPECT_BINARY_LF) { + int len = tmp->data; + if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) { + status = HCHK_STATUS_L7RSP; + desc = ist("Failed to parse hexastring resulting of eval of a log-format string"); + goto error; + } + tmp->data = len; + } + if (b_data(&check->bi) < tmp->data) { + if (!last_read) { + ret = TCPCHK_EVAL_WAIT; + goto out; + } + break; + } + match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL; + break; + + case TCPCHK_EXPECT_CUSTOM: + if (expect->custom) + ret = expect->custom(check, rule, last_read); + goto out; + default: + /* Should never happen. */ + ret = TCPCHK_EVAL_STOP; + goto out; + } + + + /* 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; + } + + /* Result as expected, next rule. */ + if (match ^ inverse) + goto out; + + error: + /* From this point on, we matched something we did not want, this is an error state. */ + ret = TCPCHK_EVAL_STOP; + msg = alloc_trash_chunk(); + if (msg) + tcpcheck_expect_onerror_message(msg, check, rule, match, desc); + set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); + free_trash_chunk(msg); + + out: + free_trash_chunk(tmp); + return ret; +} + +/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to + * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never + * waits. + */ +enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + struct act_rule *act_rule; + enum act_return act_ret; + + act_rule =rule->action_kw.rule; + act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0); + if (act_ret != ACT_RET_CONT) { + chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n", + tcpcheck_get_step_id(check, rule)); + set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area); + ret = TCPCHK_EVAL_STOP; + } + + return ret; +} + +/* Executes a tcp-check ruleset. Note that this is called both from the + * connection's wake() callback and from the check scheduling task. It returns + * 0 on normal cases, or <0 if a close() has happened on an existing connection, + * presenting the risk of an fd replacement. + * + * Please do NOT place any return statement in this function and only leave + * via the out_end_tcpcheck label after setting retcode. + */ +int tcpcheck_main(struct check *check) +{ + struct tcpcheck_rule *rule; + struct conn_stream *cs = check->cs; + struct connection *conn = cs_conn(cs); + int must_read = 1, last_read = 0; + int ret, retcode = 0; + enum tcpcheck_eval_ret eval_ret; + + /* here, we know that the check is complete or that it failed */ + if (check->result != CHK_RES_UNKNOWN) + goto out; + + /* Note: the conn-stream and the connection may only be undefined before + * the first rule evaluation (it is always a connect rule) or when the + * conn-stream allocation failed on the first connect. + */ + + /* 1- check for connection error, if any */ + if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) + goto out_end_tcpcheck; + + /* 2- check if we are waiting for the connection establishment. It only + * happens during TCPCHK_ACT_CONNECT. */ + if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) { + if (conn->flags & CO_FL_WAIT_XPRT) { + struct tcpcheck_rule *next; + + next = get_next_tcpcheck_rule(check->tcpcheck_rules, check->current_step); + if (next && next->action == TCPCHK_ACT_SEND) { + if (!(check->wait_list.events & SUB_RETRY_SEND)) + conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); + goto out; + } + else { + eval_ret = tcpcheck_eval_recv(check, check->current_step); + if (eval_ret == TCPCHK_EVAL_STOP) + goto out_end_tcpcheck; + else if (eval_ret == TCPCHK_EVAL_WAIT) + goto out; + last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS))); + must_read = 0; + } + } + rule = LIST_NEXT(&check->current_step->list, typeof(rule), list); + } + + /* 3- check for pending outgoing data. It only happens during + * TCPCHK_ACT_SEND. */ + else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) { + if (b_data(&check->bo)) { + /* We're already waiting to be able to send, give up */ + if (check->wait_list.events & SUB_RETRY_SEND) + goto out; + + ret = conn->mux->snd_buf(cs, &check->bo, + (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0); + if (ret <= 0) { + if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) + goto out_end_tcpcheck; + } + if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) { + conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list); + goto out; + } + } + rule = LIST_NEXT(&check->current_step->list, typeof(rule), list); + } + + /* 4- check if a rule must be resume. It happens if check->current_step + * is defined. */ + else if (check->current_step) + rule = check->current_step; + + /* 5- It is the first evaluation. We must create a session and preset + * tcp-check variables */ + else { + struct tcpcheck_var *var; + + /* First evaluation, create a session */ + check->sess = session_new(&checks_fe, NULL, &check->obj_type); + if (!check->sess) { + chunk_printf(&trash, "TCPCHK error allocating check session"); + set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area); + goto out_end_tcpcheck; + } + vars_init(&check->vars, SCOPE_CHECK); + rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list); + + /* Preset tcp-check variables */ + list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) { + struct sample smp; + + memset(&smp, 0, sizeof(smp)); + smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL); + smp.data = var->data; + vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp); + } + } + + /* Now evaluate the tcp-check rules */ + + list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) { + check->code = 0; + switch (rule->action) { + case TCPCHK_ACT_CONNECT: + check->current_step = rule; + + /* close but not release yet previous connection */ + if (check->cs) { + cs_close(check->cs); + retcode = -1; /* do not reuse the fd in the caller! */ + } + eval_ret = tcpcheck_eval_connect(check, rule); + + /* Refresh conn-stream and connection */ + cs = check->cs; + conn = cs_conn(cs); + must_read = 1; last_read = 0; + break; + case TCPCHK_ACT_SEND: + check->current_step = rule; + eval_ret = tcpcheck_eval_send(check, rule); + must_read = 1; + break; + case TCPCHK_ACT_EXPECT: + check->current_step = rule; + if (must_read) { + if (check->proxy->timeout.check) + check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check); + + eval_ret = tcpcheck_eval_recv(check, rule); + if (eval_ret == TCPCHK_EVAL_STOP) + goto out_end_tcpcheck; + else if (eval_ret == TCPCHK_EVAL_WAIT) + goto out; + last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS))); + must_read = 0; + } + + 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; + if (!(check->wait_list.events & SUB_RETRY_RECV)) + conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list); + } + break; + case TCPCHK_ACT_ACTION_KW: + /* Don't update the current step */ + eval_ret = tcpcheck_eval_action_kw(check, rule); + break; + default: + /* Otherwise, just go to the next one and don't update + * the current step + */ + eval_ret = TCPCHK_EVAL_CONTINUE; + break; + } + + switch (eval_ret) { + case TCPCHK_EVAL_CONTINUE: + break; + case TCPCHK_EVAL_WAIT: + goto out; + case TCPCHK_EVAL_STOP: + goto out_end_tcpcheck; + } + } + + /* All rules was evaluated */ + if (check->current_step) { + rule = check->current_step; + + if (rule->action == TCPCHK_ACT_EXPECT) { + struct buffer *msg; + enum healthcheck_status status; + + 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_expect_onsuccess_message(msg, check, rule, IST_NULL); + status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); + set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)")); + free_trash_chunk(msg); + } + else if (rule->action == TCPCHK_ACT_CONNECT) { + const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)"); + enum healthcheck_status status = HCHK_STATUS_L4OK; +#ifdef USE_OPENSSL + if (ssl_sock_is_ssl(conn)) + status = HCHK_STATUS_L6OK; +#endif + set_server_check_status(check, status, msg); + } + } + else + set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)"); + + out_end_tcpcheck: + if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) + chk_report_conn_err(check, errno, 0); + + out: + return retcode; +} + + +/**************************************************************************/ +/******************* Internals to parse tcp-check rules *******************/ +/**************************************************************************/ +struct action_kw_list tcp_check_keywords = { + .list = LIST_HEAD_INIT(tcp_check_keywords.list), +}; + +/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is + * returned on error. + */ +struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px, + struct list *rules, struct action_kw *kw, + const char *file, int line, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + struct act_rule *actrule = NULL; + + actrule = calloc(1, sizeof(*actrule)); + if (!actrule) { + memprintf(errmsg, "out of memory"); + goto error; + } + actrule->kw = kw; + actrule->from = ACT_F_TCP_CHK; + + cur_arg++; + if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) { + memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg); + goto error; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_ACTION_KW; + chk->action_kw.rule = actrule; + return chk; + + error: + free(actrule); + return NULL; +} + +/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is + * returned on error. + */ +struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + struct sockaddr_storage *sk = NULL; + char *comment = NULL, *sni = NULL, *alpn = NULL; + struct sample_expr *port_expr = NULL; + const struct mux_proto_list *mux_proto = NULL; + unsigned short conn_opts = 0; + long port = 0; + int alpn_len = 0; + + list_for_each_entry(chk, rules, list) { + if (chk->action == TCPCHK_ACT_CONNECT) + break; + 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', " + "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', " + "when there is a 'connect' step in the tcp-check ruleset"); + goto error; + } + + cur_arg++; + while (*(args[cur_arg])) { + if (strcmp(args[cur_arg], "default") == 0) + conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT; + else if (strcmp(args[cur_arg], "addr") == 0) { + int port1, port2; + struct protocol *proto; + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects as argument.", args[cur_arg]); + goto error; + } + + sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1); + if (!sk) { + memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg); + goto error; + } + + proto = protocol_by_family(sk->ss_family); + if (!proto || !proto->connect) { + memprintf(errmsg, "'%s' : connect() not supported for this address family.\n", + args[cur_arg]); + goto error; + } + + if (port1 != port2) { + memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n", + args[cur_arg], args[cur_arg+1]); + goto error; + } + + cur_arg++; + } + else if (strcmp(args[cur_arg], "port") == 0) { + const char *p, *end; + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + + port = 0; + release_sample_expr(port_expr); + p = args[cur_arg]; end = p + strlen(p); + port = read_uint(&p, end); + if (p != end) { + int idx = 0; + + px->conf.args.ctx = ARGC_SRV; + port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx, + file, line, errmsg, &px->conf.args, NULL); + + if (!port_expr) { + memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg); + goto error; + } + if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) { + memprintf(errmsg, "error detected while parsing port expression : " + " fetch method '%s' extracts information from '%s', " + "none of which is available here.\n", + args[cur_arg], sample_src_names(port_expr->fetch->use)); + goto error; + } + px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY); + } + else if (port > 65535 || port < 1) { + memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.", + args[cur_arg]); + goto error; + } + } + else if (strcmp(args[cur_arg], "proto") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]); + goto error; + } + mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1]))); + if (!mux_proto) { + memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]); + goto error; + } + cur_arg++; + } + 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 if (strcmp(args[cur_arg], "send-proxy") == 0) + conn_opts |= TCPCHK_OPT_SEND_PROXY; + else if (strcmp(args[cur_arg], "via-socks4") == 0) + conn_opts |= TCPCHK_OPT_SOCKS4; + else if (strcmp(args[cur_arg], "linger") == 0) + conn_opts |= TCPCHK_OPT_LINGER; +#ifdef USE_OPENSSL + else if (strcmp(args[cur_arg], "ssl") == 0) { + px->options |= PR_O_TCPCHK_SSL; + conn_opts |= TCPCHK_OPT_SSL; + } + else if (strcmp(args[cur_arg], "sni") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + cur_arg++; + free(sni); + sni = strdup(args[cur_arg]); + if (!sni) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + else if (strcmp(args[cur_arg], "alpn") == 0) { +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + free(alpn); + if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) { + memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg); + goto error; + } + cur_arg++; +#else + memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]); + goto error; +#endif + } +#endif /* USE_OPENSSL */ + + else { + memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'" +#ifdef USE_OPENSSL + ", 'ssl', 'sni', 'alpn'" +#endif /* USE_OPENSSL */ + " or 'via-socks4', 'linger', 'default' but got '%s' as argument.", + args[cur_arg]); + goto error; + } + cur_arg++; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_CONNECT; + chk->comment = comment; + chk->connect.port = port; + chk->connect.options = conn_opts; + chk->connect.sni = sni; + chk->connect.alpn = alpn; + chk->connect.alpn_len= alpn_len; + chk->connect.port_expr= port_expr; + chk->connect.mux_proto= mux_proto; + if (sk) + chk->connect.addr = *sk; + return chk; + + error: + free(alpn); + free(sni); + free(comment); + release_sample_expr(port_expr); + return NULL; +} + +/* Parses and creates a tcp-check send rule. NULL is returned on error */ +struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + char *comment = NULL, *data = NULL; + enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF; + + if (strcmp(args[cur_arg], "send-binary-lf") == 0) + type = TCPCHK_SEND_BINARY_LF; + else if (strcmp(args[cur_arg], "send-binary") == 0) + type = TCPCHK_SEND_BINARY; + else if (strcmp(args[cur_arg], "send-lf") == 0) + type = TCPCHK_SEND_STRING_LF; + else if (strcmp(args[cur_arg], "send") == 0) + type = TCPCHK_SEND_STRING; + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a %s as argument", + (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]); + goto error; + } + + data = args[cur_arg+1]; + + cur_arg += 2; + while (*(args[cur_arg])) { + 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' but got '%s' as argument.", + args[cur_arg]); + goto error; + } + cur_arg++; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_SEND; + chk->comment = comment; + chk->send.type = type; + + switch (chk->send.type) { + case TCPCHK_SEND_STRING: + chk->send.data = ist2(strdup(data), strlen(data)); + if (!isttest(chk->send.data)) { + memprintf(errmsg, "out of memory"); + goto error; + } + break; + case TCPCHK_SEND_BINARY: { + int len = chk->send.data.len; + if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) { + memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg); + goto error; + } + chk->send.data.len = len; + break; + } + case TCPCHK_SEND_STRING_LF: + case TCPCHK_SEND_BINARY_LF: + LIST_INIT(&chk->send.fmt); + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg); + goto error; + } + break; + case TCPCHK_SEND_HTTP: + case TCPCHK_SEND_UNDEF: + goto error; + } + + return chk; + + error: + free(chk); + free(comment); + return NULL; +} + +/* Parses and creates a http-check send rule. NULL is returned on error */ +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, host_hdr = -1; + + 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 || strcmp(args[cur_arg], "uri-lf") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT; + if (strcmp(args[cur_arg], "uri-lf") == 0) + flags |= TCPCHK_SND_HTTP_FL_URI_FMT; + cur_arg++; + uri = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "ver") == 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; + } + + if (strcasecmp(args[cur_arg+1], "host") == 0) { + if (host_hdr >= 0) { + memprintf(errmsg, "'%s' header already defined (previous value is '%s')", + args[cur_arg+1], istptr(hdrs[host_hdr].v)); + goto error; + } + host_hdr = i; + } + else if (strcasecmp(args[cur_arg+1], "connection") == 0 || + strcasecmp(args[cur_arg+1], "content-length") == 0 || + strcasecmp(args[cur_arg+1], "transfer-encoding") == 0) + goto skip_hdr; + + 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++; + skip_hdr: + cur_arg += 2; + } + else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]); + goto error; + } + flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT; + if (strcmp(args[cur_arg], "body-lf") == 0) + flags |= TCPCHK_SND_HTTP_FL_BODY_FMT; + cur_arg++; + body = args[cur_arg]; + } + 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', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'" + " 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) { + if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) { + LIST_INIT(&chk->send.http.uri_fmt); + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg); + goto error; + } + } + else { + 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; istlen(hdrs[i].n); i++) { + hdr = calloc(1, sizeof(*hdr)); + if (!hdr) { + memprintf(errmsg, "out of memory"); + goto error; + } + LIST_INIT(&hdr->value); + hdr->name = istdup(hdrs[i].n); + if (!isttest(hdr->name)) { + memprintf(errmsg, "out of memory"); + goto error; + } + + ist0(hdrs[i].v); + if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg)) + goto error; + LIST_ADDQ(&chk->send.http.hdrs, &hdr->list); + hdr = NULL; + } + + if (body) { + if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) { + LIST_INIT(&chk->send.http.body_fmt); + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg); + goto error; + } + } + else { + 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; +} + +/* Parses and creates a http-check comment rule. NULL is returned on error */ +struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules, + const char *file, int line, char **errmsg) +{ + struct tcpcheck_rule *chk = NULL; + char *comment = NULL; + + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "expects a string as argument"); + goto error; + } + cur_arg++; + comment = strdup(args[cur_arg]); + if (!comment) { + memprintf(errmsg, "out of memory"); + goto error; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_COMMENT; + chk->comment = comment; + return chk; + + error: + free(comment); + return NULL; +} + +/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned + * on error. is set to the right protocol flags (covered by the + * TCPCHK_RULES_PROTO_CHK mask). + */ +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; + struct sample_expr *status_expr = NULL; + char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat; + enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF; + enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN; + enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN; + enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN; + unsigned int flags = 0; + long min_recv = -1; + int inverse = 0; + + on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL; + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "expects at least a matching pattern as arguments"); + goto error; + } + + cur_arg++; + while (*(args[cur_arg])) { + int in_pattern = 0; + + rescan: + if (strcmp(args[cur_arg], "min-recv") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]); + goto error; + } + /* Use an signed integer here because of chksize */ + cur_arg++; + min_recv = atol(args[cur_arg]); + if (min_recv < -1 || min_recv > INT_MAX) { + memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]); + goto error; + } + } + else if (*(args[cur_arg]) == '!') { + in_pattern = 1; + while (*(args[cur_arg]) == '!') { + inverse = !inverse; + args[cur_arg]++; + } + if (!*(args[cur_arg])) + cur_arg++; + goto rescan; + } + 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; + } + if (proto != TCPCHK_RULES_HTTP_CHK) + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX); + else + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX); + + 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_BINARY_REGEX); + + 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], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) { + if (type != TCPCHK_EXPECT_UNDEF) { + memprintf(errmsg, "only on pattern expected"); + goto error; + } + if (proto != TCPCHK_RULES_HTTP_CHK) + type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF); + else { + if (*(args[cur_arg]) != 's') + goto bad_http_kw; + type = TCPCHK_EXPECT_HTTP_BODY_LF; + } + + 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_STATUS_REGEX); + + 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], "custom") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (type != TCPCHK_EXPECT_UNDEF) { + memprintf(errmsg, "only on pattern expected"); + goto error; + } + type = TCPCHK_EXPECT_CUSTOM; + } + else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) { + int orig_arg = cur_arg; + + if (proto != TCPCHK_RULES_HTTP_CHK) + goto bad_tcp_kw; + if (type != TCPCHK_EXPECT_UNDEF) { + memprintf(errmsg, "only on pattern expected"); + goto error; + } + type = TCPCHK_EXPECT_HTTP_HEADER; + + if (strcmp(args[cur_arg], "fhdr") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL; + + /* Parse the name pattern, mandatory */ + if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) || + (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) { + memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern", + args[orig_arg]); + goto error; + } + + if (strcmp(args[cur_arg+1], "name-lf") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT; + + cur_arg += 2; + if (strcmp(args[cur_arg], "-m") == 0) { + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')", + args[orig_arg], args[cur_arg]); + goto error; + } + if (strcmp(args[cur_arg+1], "str") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR; + else if (strcmp(args[cur_arg+1], "beg") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG; + else if (strcmp(args[cur_arg+1], "end") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END; + else if (strcmp(args[cur_arg+1], "sub") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB; + else if (strcmp(args[cur_arg+1], "reg") == 0) { + if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { + memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method", + args[orig_arg]); + goto error; + } + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG; + } + else { + memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')", + args[orig_arg], args[cur_arg], args[cur_arg+1]); + goto error; + } + cur_arg += 2; + } + else + flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR; + npat = args[cur_arg]; + + if (!*(args[cur_arg+1]) || + (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) { + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE; + goto next; + } + if (strcmp(args[cur_arg+1], "value-lf") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT; + + /* Parse the value pattern, optional */ + if (strcmp(args[cur_arg+2], "-m") == 0) { + cur_arg += 2; + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')", + args[orig_arg], args[cur_arg]); + goto error; + } + if (strcmp(args[cur_arg+1], "str") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR; + else if (strcmp(args[cur_arg+1], "beg") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG; + else if (strcmp(args[cur_arg+1], "end") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END; + else if (strcmp(args[cur_arg+1], "sub") == 0) + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB; + else if (strcmp(args[cur_arg+1], "reg") == 0) { + if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { + memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method", + args[orig_arg]); + goto error; + } + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG; + } + else { + memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')", + args[orig_arg], args[cur_arg], args[cur_arg+1]); + goto error; + } + } + else + flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR; + + if (!*(args[cur_arg+2])) { + memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]); + goto error; + } + vpat = args[cur_arg+2]; + cur_arg += 2; + } + else if (strcmp(args[cur_arg], "comment") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + 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 if (strcmp(args[cur_arg], "on-success") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); + goto error; + } + cur_arg++; + on_success_msg = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "on-error") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); + goto error; + } + cur_arg++; + on_error_msg = args[cur_arg]; + } + else if (strcmp(args[cur_arg], "ok-status") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); + goto error; + } + if (strcasecmp(args[cur_arg+1], "L7OK") == 0) + ok_st = HCHK_STATUS_L7OKD; + else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0) + ok_st = HCHK_STATUS_L7OKCD; + else if (strcasecmp(args[cur_arg+1], "L6OK") == 0) + ok_st = HCHK_STATUS_L6OK; + else if (strcasecmp(args[cur_arg+1], "L4OK") == 0) + ok_st = HCHK_STATUS_L4OK; + else { + memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').", + args[cur_arg], args[cur_arg+1]); + goto error; + } + cur_arg++; + } + else if (strcmp(args[cur_arg], "error-status") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); + goto error; + } + if (strcasecmp(args[cur_arg+1], "L7RSP") == 0) + err_st = HCHK_STATUS_L7RSP; + else if (strcasecmp(args[cur_arg+1], "L7STS") == 0) + err_st = HCHK_STATUS_L7STS; + else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0) + err_st = HCHK_STATUS_L6RSP; + else if (strcasecmp(args[cur_arg+1], "L4CON") == 0) + err_st = HCHK_STATUS_L4CON; + else { + memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').", + args[cur_arg], args[cur_arg+1]); + goto error; + } + cur_arg++; + } + else if (strcmp(args[cur_arg], "status-code") == 0) { + int idx = 0; + + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]); + goto error; + } + + cur_arg++; + release_sample_expr(status_expr); + px->conf.args.ctx = ARGC_SRV; + status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx, + file, line, errmsg, &px->conf.args, NULL); + if (!status_expr) { + memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg); + goto error; + } + if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) { + memprintf(errmsg, "error detected while parsing status-code expression : " + " fetch method '%s' extracts information from '%s', " + "none of which is available here.\n", + args[cur_arg], sample_src_names(status_expr->fetch->use)); + goto error; + } + px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY); + } + else if (strcmp(args[cur_arg], "tout-status") == 0) { + if (in_pattern) { + memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]); + goto error; + } + if (!*(args[cur_arg+1])) { + memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]); + goto error; + } + if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0) + tout_st = HCHK_STATUS_L7TOUT; + else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0) + tout_st = HCHK_STATUS_L6TOUT; + else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0) + tout_st = HCHK_STATUS_L4TOUT; + else { + memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').", + args[cur_arg], args[cur_arg+1]); + goto error; + } + cur_arg++; + } + else { + if (proto == TCPCHK_RULES_HTTP_CHK) { + bad_http_kw: + memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', " + "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]); + } + else { + bad_tcp_kw: + memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'" + "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]); + } + goto error; + } + next: + cur_arg++; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->action = TCPCHK_ACT_EXPECT; + LIST_INIT(&chk->expect.onerror_fmt); + LIST_INIT(&chk->expect.onsuccess_fmt); + chk->comment = comment; comment = NULL; + chk->expect.type = type; + chk->expect.min_recv = min_recv; + chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0); + chk->expect.ok_status = ok_st; + chk->expect.err_status = err_st; + chk->expect.tout_status = tout_st; + chk->expect.status_expr = status_expr; status_expr = NULL; + + if (on_success_msg) { + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg); + goto error; + } + } + if (on_error_msg) { + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg); + goto error; + } + } + + switch (chk->expect.type) { + case TCPCHK_EXPECT_HTTP_STATUS: { + const char *p = pattern; + unsigned int c1,c2; + + chk->expect.codes.codes = NULL; + chk->expect.codes.num = 0; + while (1) { + c1 = c2 = read_uint(&p, pattern + strlen(pattern)); + if (*p == '-') { + p++; + c2 = read_uint(&p, pattern + strlen(pattern)); + } + if (c1 > c2) { + memprintf(errmsg, "invalid range of status codes '%s'", pattern); + goto error; + } + + chk->expect.codes.num++; + chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes, + chk->expect.codes.num * sizeof(*chk->expect.codes.codes)); + if (!chk->expect.codes.codes) { + memprintf(errmsg, "out of memory"); + goto error; + } + chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1; + chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2; + + if (*p == '\0') + break; + if (*p != ',') { + memprintf(errmsg, "invalid character '%c' in the list of status codes", *p); + goto error; + } + p++; + } + break; + } + case TCPCHK_EXPECT_STRING: + case TCPCHK_EXPECT_HTTP_BODY: + chk->expect.data = ist2(strdup(pattern), strlen(pattern)); + if (!isttest(chk->expect.data)) { + memprintf(errmsg, "out of memory"); + goto error; + } + break; + case TCPCHK_EXPECT_BINARY: { + int len = chk->expect.data.len; + + if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) { + memprintf(errmsg, "invalid binary string (%s)", *errmsg); + goto error; + } + chk->expect.data.len = len; + break; + } + case TCPCHK_EXPECT_STRING_REGEX: + case TCPCHK_EXPECT_BINARY_REGEX: + case TCPCHK_EXPECT_HTTP_STATUS_REGEX: + case TCPCHK_EXPECT_HTTP_BODY_REGEX: + chk->expect.regex = regex_comp(pattern, 1, 0, errmsg); + if (!chk->expect.regex) + goto error; + break; + + case TCPCHK_EXPECT_STRING_LF: + case TCPCHK_EXPECT_BINARY_LF: + case TCPCHK_EXPECT_HTTP_BODY_LF: + LIST_INIT(&chk->expect.fmt); + px->conf.args.ctx = ARGC_SRV; + if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg); + goto error; + } + break; + + case TCPCHK_EXPECT_HTTP_HEADER: + if (!npat) { + memprintf(errmsg, "unexpected error, undefined header name pattern"); + goto error; + } + if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) { + chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg); + if (!chk->expect.hdr.name_re) + goto error; + } + else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) { + px->conf.args.ctx = ARGC_SRV; + LIST_INIT(&chk->expect.hdr.name_fmt); + if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg); + goto error; + } + } + else { + chk->expect.hdr.name = ist2(strdup(npat), strlen(npat)); + if (!isttest(chk->expect.hdr.name)) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + + if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) { + chk->expect.hdr.value = IST_NULL; + break; + } + + if (!vpat) { + memprintf(errmsg, "unexpected error, undefined header value pattern"); + goto error; + } + else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) { + chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg); + if (!chk->expect.hdr.value_re) + goto error; + } + else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) { + px->conf.args.ctx = ARGC_SRV; + LIST_INIT(&chk->expect.hdr.value_fmt); + if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) { + memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg); + goto error; + } + } + else { + chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat)); + if (!isttest(chk->expect.hdr.value)) { + memprintf(errmsg, "out of memory"); + goto error; + } + } + + break; + case TCPCHK_EXPECT_CUSTOM: + chk->expect.custom = NULL; /* Must be defined by the caller ! */ + break; + case TCPCHK_EXPECT_UNDEF: + memprintf(errmsg, "pattern not found"); + goto error; + } + + /* All tcp-check expect points back to the first inverse expect rule in + * a chain of one or more expect rule, potentially itself. + */ + chk->expect.head = chk; + list_for_each_entry_rev(prev_check, rules, list) { + if (prev_check->action == TCPCHK_ACT_EXPECT) { + if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV) + chk->expect.head = prev_check; + continue; + } + if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW) + break; + } + return chk; + + error: + free_tcpcheck(chk, 0); + free(comment); + release_sample_expr(status_expr); + return NULL; +} + +/* Overwrites fields of the old http send rule with those of the new one. When + * replaced, old values are freed and replaced by the new ones. New values are + * not copied but transferred. At the end should be empty and can be + * safely released. This function never fails. + */ +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)) + istfree(&old->send.http.uri); + 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)) + istfree(&old->send.http.uri); + 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)) { + istfree(&old->send.http.vsn); + 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)) + istfree(&old->send.http.body); + 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)) + istfree(&old->send.http.body); + 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); + } + } +} + +/* Internal function used to add an http-check rule in a list during the config + * parsing step. Depending on its type, and the previously inserted rules, a + * specific action may be performed or an error may be reported. This functions + * returns 1 on success and 0 on error and is filled with the error + * message. + */ +int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg) +{ + struct tcpcheck_rule *r; + + /* the implicit send rule coming from an "option httpchk" line must be + * merged with the first explici http-check send rule, if + * any. Depdending the declaration order some tests are required. + * + * Some tests is also required for other kinds of http-check rules to be + * sure the ruleset remains valid. + */ + + if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) { + /* Tries to add an implicit http-check send rule from an "option httpchk" line. + * First, the first rule is retrieved, skipping the first CONNECT, if any, and + * following tests are performed : + * + * 1- If there is no such rule or if it is not a send rule, the implicit send + * rule is pushed in front of the ruleset + * + * 2- If it is another implicit send rule, it is replaced with the new one. + * + * 3- Otherwise, it means it is an explicit send rule. In this case we merge + * both, overwriting the old send rule (the explicit one) with info of the + * new send rule (the implicit one). + */ + 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 { + /* Tries to add an explicit http-check rule. First of all we check the typefo the + * last inserted rule to be sure it is valid. Then for send rule, we try to merge it + * with an existing implicit send rule, if any. At the end, if there is no error, + * the rule is appended to the list. + */ + + 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 && r->action != TCPCHK_ACT_EXPECT && 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; +} + +/* Check tcp-check health-check configuration for the proxy . */ +static int check_proxy_tcpcheck(struct proxy *px) +{ + struct tcpcheck_rule *chk, *back; + 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) { + 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); + ret |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* HTTP ruleset only : */ + if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) { + struct tcpcheck_rule *next; + + /* move remaining implicit send rule from "option httpchk" line to the right place. + * If such rule exists, it must be the first one. In this case, the rule is moved + * after the first connect rule, if any. Otherwise, nothing is done. + */ + 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. It is inherited from previous + * versions where the http expect rule was optional. Now it is possible to chained + * send/expect rules but the last expect may still be implicit. + */ + chk = get_last_tcpcheck_rule(&px->tcpcheck_rules); + if (chk && chk->action == TCPCHK_ACT_SEND) { + next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""}, + 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; + } + } + + /* For all ruleset: */ + + /* If there is no connect rule preceding all send / expect rules, an + * implicit one is inserted before all others. + */ + chk = get_first_tcpcheck_rule(&px->tcpcheck_rules); + if (!chk || chk->action != TCPCHK_ACT_CONNECT) { + chk = calloc(1, sizeof(*chk)); + if (!chk) { + ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule " + "(out of memory).\n", px->id); + ret |= ERR_ALERT | ERR_FATAL; + goto out; + } + chk->action = TCPCHK_ACT_CONNECT; + chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT); + LIST_ADD(px->tcpcheck_rules.list, &chk->list); + } + + /* Remove all comment rules. To do so, when a such rule is found, the + * comment is assigned to the following rule(s). + */ + list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) { + if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) { + free(comment); + comment = NULL; + } + + prev_action = chk->action; + switch (chk->action) { + case TCPCHK_ACT_COMMENT: + free(comment); + comment = chk->comment; + LIST_DEL(&chk->list); + free(chk); + break; + case TCPCHK_ACT_CONNECT: + if (!chk->comment && comment) + chk->comment = strdup(comment); + /* fall though */ + case TCPCHK_ACT_ACTION_KW: + free(comment); + comment = NULL; + break; + case TCPCHK_ACT_SEND: + case TCPCHK_ACT_EXPECT: + if (!chk->comment && comment) + chk->comment = strdup(comment); + break; + } + } + free(comment); + comment = NULL; + + out: + return ret; +} + +void deinit_proxy_tcpcheck(struct proxy *px) +{ + free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars); + px->tcpcheck_rules.flags = 0; + px->tcpcheck_rules.list = NULL; +} + +static void deinit_tcpchecks() +{ + struct tcpcheck_ruleset *rs; + struct tcpcheck_rule *r, *rb; + struct ebpt_node *node, *next; + + node = ebpt_first(&shared_tcpchecks); + while (node) { + next = ebpt_next(node); + ebpt_delete(node); + free(node->key); + rs = container_of(node, typeof(*rs), node); + list_for_each_entry_safe(r, rb, &rs->rules, list) { + LIST_DEL(&r->list); + free_tcpcheck(r, 0); + } + free(rs); + node = next; + } +} + +int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str) +{ + struct tcpcheck_rule *tcpcheck, *prev_check; + struct tcpcheck_expect *expect; + + if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL) + return 0; + memset(tcpcheck, 0, sizeof(*tcpcheck)); + tcpcheck->action = TCPCHK_ACT_EXPECT; + + expect = &tcpcheck->expect; + expect->type = TCPCHK_EXPECT_STRING; + LIST_INIT(&expect->onerror_fmt); + LIST_INIT(&expect->onsuccess_fmt); + expect->ok_status = HCHK_STATUS_L7OKD; + expect->err_status = HCHK_STATUS_L7RSP; + expect->tout_status = HCHK_STATUS_L7TOUT; + expect->data = ist2(strdup(str), strlen(str)); + if (!isttest(expect->data)) { + pool_free(pool_head_tcpcheck_rule, tcpcheck); + return 0; + } + + /* All tcp-check expect points back to the first inverse expect rule + * in a chain of one or more expect rule, potentially itself. + */ + tcpcheck->expect.head = tcpcheck; + list_for_each_entry_rev(prev_check, rules->list, list) { + if (prev_check->action == TCPCHK_ACT_EXPECT) { + if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV) + tcpcheck->expect.head = prev_check; + continue; + } + if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW) + break; + } + LIST_ADDQ(rules->list, &tcpcheck->list); + return 1; +} + +int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs) +{ + struct tcpcheck_rule *tcpcheck; + struct tcpcheck_send *send; + const char *in; + char *dst; + int i; + + if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL) + return 0; + memset(tcpcheck, 0, sizeof(*tcpcheck)); + tcpcheck->action = TCPCHK_ACT_SEND; + + send = &tcpcheck->send; + send->type = TCPCHK_SEND_STRING; + + for (i = 0; strs[i]; i++) + send->data.len += strlen(strs[i]); + + send->data.ptr = malloc(istlen(send->data) + 1); + if (!isttest(send->data)) { + pool_free(pool_head_tcpcheck_rule, tcpcheck); + return 0; + } + + dst = istptr(send->data); + for (i = 0; strs[i]; i++) + for (in = strs[i]; (*dst = *in++); dst++); + *dst = 0; + + LIST_ADDQ(rules->list, &tcpcheck->list); + return 1; +} + +/* Parses the "tcp-check" proxy keyword */ +static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **errmsg) +{ + 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; + + /* Deduce the ruleset name from the proxy info */ + chunk_printf(&trash, "*tcp-check-%s_%s-%d", + ((curpx == defpx) ? "defaults" : curpx->id), + curpx->conf.file, curpx->conf.line); + + rs = find_tcpcheck_ruleset(b_orig(&trash)); + if (rs == NULL) { + rs = create_tcpcheck_ruleset(b_orig(&trash)); + if (rs == NULL) { + memprintf(errmsg, "out of memory.\n"); + goto error; + } + } + + index = 0; + if (!LIST_ISEMPTY(&rs->rules)) { + chk = LIST_PREV(&rs->rules, typeof(chk), list); + index = chk->index + 1; + } + + cur_arg = 1; + 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 || strcmp(args[cur_arg], "send-binary") == 0 || + strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 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, 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 { + struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]); + + if (!kw) { + action_kw_tcp_check_build_list(&trash); + memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'" + "%s%s. but got '%s'", + args[0], (*trash.area ? ", " : ""), trash.area, args[1]); + goto error; + } + 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 = (ret || (*errmsg != NULL)); /* Handle warning */ + + /* No error: add the tcp-check rule in the list */ + chk->index = index; + LIST_ADDQ(&rs->rules, &chk->list); + + if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && + (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) { + /* Use this ruleset if the proxy already has tcp-check enabled */ + curpx->tcpcheck_rules.list = &rs->rules; + curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS; + } + else { + /* mark this ruleset as unused for now */ + curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS; + } + + return ret; + + error: + free_tcpcheck(chk, 0); + free_tcpcheck_ruleset(rs); + return -1; +} + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck }, + { 0, NULL, NULL }, +}}; + +REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck); +REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck); +REGISTER_POST_DEINIT(deinit_tcpchecks); +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); diff --git a/src/vars.c b/src/vars.c index d58e1a325..2e3258300 100644 --- a/src/vars.c +++ b/src/vars.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include