blob: fa595ea6593e63e669b582ec350f9947b6eb561e [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020041
42#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020043#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010044#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020045
Gaetan Rivet707b52f2020-02-21 18:14:59 +010046#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020047#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020049#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010050#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/fd.h>
52#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020053#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020055#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010056#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010057#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010058#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020059#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020060#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010061#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020062#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010063#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020064#include <proto/log.h>
65#include <proto/dns.h>
66#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020067#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020068#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020069
Willy Tarreaubd741542010-03-16 18:46:54 +010070static int httpchk_expect(struct server *s, int done);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020071static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Willy Tarreau6bdcab02017-10-04 18:41:00 +020072static int tcpcheck_main(struct check *);
Olivier Houchard910b2bc2018-07-17 18:49:38 +020073static void __event_srv_chk_w(struct conn_stream *cs);
Olivier Houchard4501c3e2018-08-28 19:36:18 +020074static int wake_srv_chk(struct conn_stream *cs);
Olivier Houchardaf4021e2018-08-09 13:06:55 +020075static void __event_srv_chk_r(struct conn_stream *cs);
Willy Tarreaubd741542010-03-16 18:46:54 +010076
Christopher Faulet31c30fd2020-03-26 21:10:03 +010077static int srv_check_healthcheck_port(struct check *chk);
78
Christopher Faulet5d503fc2020-03-30 20:34:34 +020079/* Global list to share all tcp-checks */
80struct list tcpchecks_list = LIST_HEAD_INIT(tcpchecks_list);
81
82
Willy Tarreau8ceae722018-11-26 11:58:30 +010083DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
84DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020085
Gaetan Rivet05d692d2020-02-14 17:42:54 +010086/* Dummy frontend used to create all checks sessions. */
87static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Simon Horman63a4a822012-03-19 07:24:41 +090089static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010090 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
91 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +020092 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020093
Willy Tarreau23964182014-05-20 20:56:30 +020094 /* Below we have finished checks */
95 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010096 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +010097
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010098 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020099
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100100 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
101 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
102 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200103
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100104 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
105 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
106 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200107
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100108 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
109 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200111 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200112
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
114 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
115 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900116
117 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
118 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200119 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120};
121
Cyril Bontéac92a062014-12-27 22:28:38 +0100122const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
123 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
124 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
125 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
126 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
127 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
128 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
129 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
Christopher Fauletaaae9a02020-04-26 09:50:31 +0200130 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
131 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
Cyril Bontéac92a062014-12-27 22:28:38 +0100132 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
133 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
134};
135
Simon Horman63a4a822012-03-19 07:24:41 +0900136static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100137 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
138
139 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
140 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
141
142 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
143 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
144 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
145 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
146
147 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
148 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
149 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
150};
151
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100152/* checks if <err> is a real error for errno or one that can be ignored, and
153 * return 0 for these ones or <err> for real ones.
154 */
155static inline int unclean_errno(int err)
156{
157 if (err == EAGAIN || err == EINPROGRESS ||
158 err == EISCONN || err == EALREADY)
159 return 0;
160 return err;
161}
162
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200163/*
164 * Convert check_status code to description
165 */
166const char *get_check_status_description(short check_status) {
167
168 const char *desc;
169
170 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200171 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200172 else
173 desc = NULL;
174
175 if (desc && *desc)
176 return desc;
177 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200178 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200179}
180
181/*
182 * Convert check_status code to short info
183 */
184const char *get_check_status_info(short check_status) {
185
186 const char *info;
187
188 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200189 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200190 else
191 info = NULL;
192
193 if (info && *info)
194 return info;
195 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200196 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200197}
198
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200214/*
Simon Horman4a741432013-02-23 15:35:38 +0900215 * Set check->status, update check->duration and fill check->result with
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200216 * an adequate CHK_RES_* value. The new check->health is computed based
217 * on the result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200218 *
219 * Show information in logs about failed health check if server is UP
220 * or succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200221 */
Simon Horman4a741432013-02-23 15:35:38 +0900222static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100223{
Simon Horman4a741432013-02-23 15:35:38 +0900224 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200225 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200226 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900227
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200228 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100229 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900230 check->desc[0] = '\0';
231 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200232 return;
233 }
234
Simon Horman4a741432013-02-23 15:35:38 +0900235 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200236 return;
237
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200238 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900239 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
240 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200241 } else
Simon Horman4a741432013-02-23 15:35:38 +0900242 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200243
Simon Horman4a741432013-02-23 15:35:38 +0900244 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200245 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900246 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200247
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100248 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900249 check->duration = -1;
250 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200251 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900252 check->duration = tv_ms_elapsed(&check->start, &now);
253 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200254 }
255
Willy Tarreau23964182014-05-20 20:56:30 +0200256 /* no change is expected if no state change occurred */
257 if (check->result == CHK_RES_NEUTRAL)
258 return;
259
Olivier Houchard0923fa42019-01-11 18:43:04 +0100260 /* If the check was really just sending a mail, it won't have an
261 * associated server, so we're done now.
262 */
263 if (!s)
264 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200265 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200266
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200267 switch (check->result) {
268 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200269 /* Failure to connect to the agent as a secondary check should not
270 * cause the server to be marked down.
271 */
272 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900273 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200274 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100275 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200276 report = 1;
277 check->health--;
278 if (check->health < check->rise)
279 check->health = 0;
280 }
281 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200282
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200283 case CHK_RES_PASSED:
284 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
285 if ((check->health < check->rise + check->fall - 1) &&
286 (check->result == CHK_RES_PASSED || check->health > 0)) {
287 report = 1;
288 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200289
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200290 if (check->health >= check->rise)
291 check->health = check->rise + check->fall - 1; /* OK now */
292 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200293
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200294 /* clear consecutive_errors if observing is enabled */
295 if (s->onerror)
296 s->consecutive_errors = 0;
297 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100298
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200299 default:
300 break;
301 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200302
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200303 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
304 (status != prev_status || report)) {
305 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200306 "%s check for %sserver %s/%s %s%s",
307 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200308 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100309 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100310 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200311 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200312
Emeric Brun5a133512017-10-19 14:42:30 +0200313 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200314
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100315 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200316 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
317 (check->health >= check->rise) ? check->fall : check->rise,
318 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200319
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200320 ha_warning("%s.\n", trash.area);
321 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
322 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200323 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200324}
325
Willy Tarreau4eec5472014-05-20 22:32:27 +0200326/* Marks the check <check>'s server down if the current check is already failed
327 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200328 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200329static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200330{
Simon Horman4a741432013-02-23 15:35:38 +0900331 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900332
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200333 /* The agent secondary check should only cause a server to be marked
334 * as down if check->status is HCHK_STATUS_L7STS, which indicates
335 * that the agent returned "fail", "stopped" or "down".
336 * The implication here is that failure to connect to the agent
337 * as a secondary check should not cause the server to be marked
338 * down. */
339 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
340 return;
341
Willy Tarreau4eec5472014-05-20 22:32:27 +0200342 if (check->health > 0)
343 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100344
Willy Tarreau4eec5472014-05-20 22:32:27 +0200345 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200346 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200347}
348
Willy Tarreauaf549582014-05-16 17:37:50 +0200349/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200350 * it isn't in maintenance, it is not tracking a down server and other checks
351 * comply. The rule is simple : by default, a server is up, unless any of the
352 * following conditions is true :
353 * - health check failed (check->health < rise)
354 * - agent check failed (agent->health < rise)
355 * - the server tracks a down server (track && track->state == STOPPED)
356 * Note that if the server has a slowstart, it will switch to STARTING instead
357 * of RUNNING. Also, only the health checks support the nolb mode, so the
358 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200359 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200360static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200361{
Simon Horman4a741432013-02-23 15:35:38 +0900362 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100363
Emeric Brun52a91d32017-08-31 14:41:55 +0200364 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200365 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100366
Emeric Brun52a91d32017-08-31 14:41:55 +0200367 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200368 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100369
Willy Tarreau3e048382014-05-21 10:30:54 +0200370 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
371 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100372
Willy Tarreau3e048382014-05-21 10:30:54 +0200373 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
374 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200375
Emeric Brun52a91d32017-08-31 14:41:55 +0200376 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200377 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100378
Emeric Brun5a133512017-10-19 14:42:30 +0200379 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100380}
381
Willy Tarreaudb58b792014-05-21 13:57:23 +0200382/* Marks the check <check> as valid and tries to set its server into stopping mode
383 * if it was running or starting, and provided it isn't in maintenance and other
384 * checks comply. The conditions for the server to be marked in stopping mode are
385 * the same as for it to be turned up. Also, only the health checks support the
386 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200387 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200388static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200389{
Simon Horman4a741432013-02-23 15:35:38 +0900390 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100391
Emeric Brun52a91d32017-08-31 14:41:55 +0200392 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200393 return;
394
Willy Tarreaudb58b792014-05-21 13:57:23 +0200395 if (check->state & CHK_ST_AGENT)
396 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100397
Emeric Brun52a91d32017-08-31 14:41:55 +0200398 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200399 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100400
Willy Tarreaudb58b792014-05-21 13:57:23 +0200401 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
402 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100403
Willy Tarreaudb58b792014-05-21 13:57:23 +0200404 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
405 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100406
Willy Tarreaub26881a2017-12-23 11:16:49 +0100407 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100408}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200409
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100410/* note: use health_adjust() only, which first checks that the observe mode is
411 * enabled.
412 */
413void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100414{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100415 int failed;
416 int expire;
417
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100418 if (s->observe >= HANA_OBS_SIZE)
419 return;
420
Willy Tarreaubb956662013-01-24 00:37:39 +0100421 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100422 return;
423
424 switch (analyze_statuses[status].lr[s->observe - 1]) {
425 case 1:
426 failed = 1;
427 break;
428
429 case 2:
430 failed = 0;
431 break;
432
433 default:
434 return;
435 }
436
437 if (!failed) {
438 /* good: clear consecutive_errors */
439 s->consecutive_errors = 0;
440 return;
441 }
442
Olivier Houchard7059c552019-03-08 18:49:32 +0100443 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100444
445 if (s->consecutive_errors < s->consecutive_errors_limit)
446 return;
447
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100448 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
449 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100450
451 switch (s->onerror) {
452 case HANA_ONERR_FASTINTER:
453 /* force fastinter - nothing to do here as all modes force it */
454 break;
455
456 case HANA_ONERR_SUDDTH:
457 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900458 if (s->check.health > s->check.rise)
459 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100460
461 /* no break - fall through */
462
463 case HANA_ONERR_FAILCHK:
464 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200465 set_server_check_status(&s->check, HCHK_STATUS_HANA,
466 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200467 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100468 break;
469
470 case HANA_ONERR_MARKDWN:
471 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900472 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200473 set_server_check_status(&s->check, HCHK_STATUS_HANA,
474 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200475 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100476 break;
477
478 default:
479 /* write a warning? */
480 break;
481 }
482
483 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100484 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100485
Simon Horman66183002013-02-23 10:16:43 +0900486 if (s->check.fastinter) {
487 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300488 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200489 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300490 /* requeue check task with new expire */
491 task_queue(s->check.task);
492 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100493 }
494}
495
Willy Tarreaua1dab552014-04-14 15:04:54 +0200496static int httpchk_build_status_header(struct server *s, char *buffer, int size)
Willy Tarreauef781042010-01-27 11:53:01 +0100497{
498 int sv_state;
499 int ratio;
500 int hlen = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800501 char addr[46];
502 char port[6];
Willy Tarreauef781042010-01-27 11:53:01 +0100503 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
504 "UP %d/%d", "UP",
505 "NOLB %d/%d", "NOLB",
506 "no check" };
507
508 memcpy(buffer + hlen, "X-Haproxy-Server-State: ", 24);
509 hlen += 24;
510
Willy Tarreauff5ae352013-12-11 20:36:34 +0100511 if (!(s->check.state & CHK_ST_ENABLED))
512 sv_state = 6;
Emeric Brun52a91d32017-08-31 14:41:55 +0200513 else if (s->cur_state != SRV_ST_STOPPED) {
Simon Horman58c32972013-11-25 10:46:38 +0900514 if (s->check.health == s->check.rise + s->check.fall - 1)
Willy Tarreauef781042010-01-27 11:53:01 +0100515 sv_state = 3; /* UP */
516 else
517 sv_state = 2; /* going down */
518
Emeric Brun52a91d32017-08-31 14:41:55 +0200519 if (s->cur_state == SRV_ST_STOPPING)
Willy Tarreauef781042010-01-27 11:53:01 +0100520 sv_state += 2;
521 } else {
Simon Horman125d0992013-02-24 17:23:38 +0900522 if (s->check.health)
Willy Tarreauef781042010-01-27 11:53:01 +0100523 sv_state = 1; /* going up */
524 else
525 sv_state = 0; /* DOWN */
526 }
527
Willy Tarreaua1dab552014-04-14 15:04:54 +0200528 hlen += snprintf(buffer + hlen, size - hlen,
Willy Tarreauef781042010-01-27 11:53:01 +0100529 srv_hlt_st[sv_state],
Emeric Brun52a91d32017-08-31 14:41:55 +0200530 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
531 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreauef781042010-01-27 11:53:01 +0100532
Joseph Lynch514061c2015-01-15 17:52:59 -0800533 addr_to_str(&s->addr, addr, sizeof(addr));
Willy Tarreau04276f32017-01-06 17:41:29 +0100534 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
535 snprintf(port, sizeof(port), "%u", s->svc_port);
536 else
537 *port = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800538
539 hlen += snprintf(buffer + hlen, size - hlen, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
540 addr, port, s->proxy->id, s->id,
Willy Tarreauef781042010-01-27 11:53:01 +0100541 global.node,
Emeric Brun52a91d32017-08-31 14:41:55 +0200542 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
Willy Tarreauef781042010-01-27 11:53:01 +0100543 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
544 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
545 s->nbpend);
546
Emeric Brun52a91d32017-08-31 14:41:55 +0200547 if ((s->cur_state == SRV_ST_STARTING) &&
Willy Tarreauef781042010-01-27 11:53:01 +0100548 now.tv_sec < s->last_change + s->slowstart &&
549 now.tv_sec >= s->last_change) {
550 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
Willy Tarreaua1dab552014-04-14 15:04:54 +0200551 hlen += snprintf(buffer + hlen, size - hlen, "; throttle=%d%%", ratio);
Willy Tarreauef781042010-01-27 11:53:01 +0100552 }
553
554 buffer[hlen++] = '\r';
555 buffer[hlen++] = '\n';
556
557 return hlen;
558}
559
Willy Tarreau20a18342013-12-05 00:31:46 +0100560/* Check the connection. If an error has already been reported or the socket is
561 * closed, keep errno intact as it is supposed to contain the valid error code.
562 * If no error is reported, check the socket's error queue using getsockopt().
563 * Warning, this must be done only once when returning from poll, and never
564 * after an I/O error was attempted, otherwise the error queue might contain
565 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
566 * socket. Returns non-zero if an error was reported, zero if everything is
567 * clean (including a properly closed socket).
568 */
569static int retrieve_errno_from_socket(struct connection *conn)
570{
571 int skerr;
572 socklen_t lskerr = sizeof(skerr);
573
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100574 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100575 return 1;
576
Willy Tarreau3c728722014-01-23 13:50:42 +0100577 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100578 return 0;
579
Willy Tarreau585744b2017-08-24 14:31:19 +0200580 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100581 errno = skerr;
582
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100583 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100584
585 if (!errno) {
586 /* we could not retrieve an error, that does not mean there is
587 * none. Just don't change anything and only report the prior
588 * error if any.
589 */
590 if (conn->flags & CO_FL_ERROR)
591 return 1;
592 else
593 return 0;
594 }
595
596 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
597 return 1;
598}
599
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100600/* Try to collect as much information as possible on the connection status,
601 * and adjust the server status accordingly. It may make use of <errno_bck>
602 * if non-null when the caller is absolutely certain of its validity (eg:
603 * checked just after a syscall). If the caller doesn't have a valid errno,
604 * it can pass zero, and retrieve_errno_from_socket() will be called to try
605 * to extract errno from the socket. If no error is reported, it will consider
606 * the <expired> flag. This is intended to be used when a connection error was
607 * reported in conn->flags or when a timeout was reported in <expired>. The
608 * function takes care of not updating a server status which was already set.
609 * All situations where at least one of <expired> or CO_FL_ERROR are set
610 * produce a status.
611 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200612static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100613{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200614 struct conn_stream *cs = check->cs;
615 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100616 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200617 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200618 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100619
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100620 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100621 return;
622
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100623 errno = unclean_errno(errno_bck);
624 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100625 retrieve_errno_from_socket(conn);
626
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200627 if (conn && !(conn->flags & CO_FL_ERROR) &&
628 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100629 return;
630
631 /* we'll try to build a meaningful error message depending on the
632 * context of the error possibly present in conn->err_code, and the
633 * socket error possibly collected above. This is useful to know the
634 * exact step of the L6 layer (eg: SSL handshake).
635 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200636 chk = get_trash_chunk();
637
638 if (check->type == PR_O2_TCPCHK_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200639 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200640 if (!step)
641 chunk_printf(chk, " at initial connection step of tcp-check");
642 else {
643 chunk_printf(chk, " at step %d of tcp-check", step);
644 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200645 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
646 if (check->current_step->connect.port)
647 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200648 else
649 chunk_appendf(chk, " (connect)");
650 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200651 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
652 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100653
654 switch (expect->type) {
655 case TCPCHK_EXPECT_STRING:
656 chunk_appendf(chk, " (expect string '%s')", expect->string);
657 break;
658 case TCPCHK_EXPECT_BINARY:
659 chunk_appendf(chk, " (expect binary '%s')", expect->string);
660 break;
661 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200662 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100663 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100664 case TCPCHK_EXPECT_REGEX_BINARY:
665 chunk_appendf(chk, " (expect binary regex)");
666 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200667 case TCPCHK_EXPECT_CUSTOM:
668 chunk_appendf(chk, " (expect custom function)");
669 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100670 case TCPCHK_EXPECT_UNDEF:
671 chunk_appendf(chk, " (undefined expect!)");
672 break;
673 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200674 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200675 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200676 chunk_appendf(chk, " (send)");
677 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200678
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200679 if (check->current_step && check->current_step->comment)
680 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200681 }
682 }
683
Willy Tarreau00149122017-10-04 18:05:01 +0200684 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100685 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200686 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
687 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100688 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200689 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
690 chk->area);
691 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100692 }
693 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100694 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200695 chunk_printf(&trash, "%s%s", strerror(errno),
696 chk->area);
697 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100698 }
699 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200700 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100701 }
702 }
703
Willy Tarreau00149122017-10-04 18:05:01 +0200704 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200705 /* NOTE: this is reported after <fall> tries */
706 chunk_printf(chk, "No port available for the TCP connection");
707 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
708 }
709
Willy Tarreau00149122017-10-04 18:05:01 +0200710 if (!conn) {
711 /* connection allocation error before the connection was established */
712 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
713 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100714 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100715 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200716 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100717 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
718 else if (expired)
719 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200720
721 /*
722 * might be due to a server IP change.
723 * Let's trigger a DNS resolution if none are currently running.
724 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100725 if (check->server)
726 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200727
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100728 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100729 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100730 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200731 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100732 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
733 else if (expired)
734 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
735 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200736 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100737 /* I/O error after connection was established and before we could diagnose */
738 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
739 }
740 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200741 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
742
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100743 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200744 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
745 tout = check->current_step->expect.tout_status;
746 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100747 }
748
749 return;
750}
751
Olivier Houchard5c110b92018-08-14 17:04:58 +0200752/* This function checks if any I/O is wanted, and if so, attempts to do so */
753static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200754{
Olivier Houchard26e1a8f2018-09-12 15:15:12 +0200755 struct check *check = ctx;
756 struct conn_stream *cs = check->cs;
Olivier Houchard0923fa42019-01-11 18:43:04 +0100757 struct email_alertq *q = container_of(check, typeof(*q), check);
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200758 int ret = 0;
Olivier Houchard4501c3e2018-08-28 19:36:18 +0200759
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100760 if (!(check->wait_list.events & SUB_RETRY_SEND))
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200761 ret = wake_srv_chk(cs);
762 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
Olivier Houchard0923fa42019-01-11 18:43:04 +0100763 if (check->server)
764 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
765 else
766 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200767 __event_srv_chk_r(cs);
Olivier Houchard0923fa42019-01-11 18:43:04 +0100768 if (check->server)
769 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
770 else
771 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200772 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200773 return NULL;
774}
775
776/* same as above but protected by the server lock.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100777 *
778 * Please do NOT place any return statement in this function and only leave
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200779 * via the out label. NOTE THAT THIS FUNCTION DOESN'T LOCK, YOU PROBABLY WANT
780 * TO USE event_srv_chk_w() instead.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200781 */
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200782static void __event_srv_chk_w(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200783{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200784 struct connection *conn = cs->conn;
785 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900786 struct server *s = check->server;
Simon Horman4a741432013-02-23 15:35:38 +0900787 struct task *t = check->task;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200788
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100789 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100790 goto out_wakeup;
791
Willy Tarreau20a18342013-12-05 00:31:46 +0100792 if (retrieve_errno_from_socket(conn)) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200793 chk_report_conn_err(check, errno, 0);
Willy Tarreau20a18342013-12-05 00:31:46 +0100794 goto out_wakeup;
795 }
Krzysztof Piotr Oledzki6492db52010-01-02 22:03:01 +0100796
Willy Tarreau06559ac2013-12-05 01:53:08 +0100797 /* here, we know that the connection is established. That's enough for
798 * a pure TCP check.
799 */
800 if (!check->type)
801 goto out_wakeup;
802
Willy Tarreauc09572f2017-10-04 11:58:22 +0200803 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100804 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200805 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200806
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200807 if (b_data(&check->bo)) {
Olivier Houcharded0f2072018-08-16 15:41:52 +0200808 cs->conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200809 b_realign_if_empty(&check->bo);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200810 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200811 chk_report_conn_err(check, errno, 0);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100812 goto out_wakeup;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200813 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200814 if (b_data(&check->bo)) {
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100815 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200816 goto out;
817 }
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100818 }
Willy Tarreau6996e152007-04-30 14:37:43 +0200819
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100820 /* full request sent, we allow up to <timeout.check> if nonzero for a response */
821 if (s->proxy->timeout.check) {
822 t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
823 task_queue(t);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200824 }
Olivier Houchard53216e72018-10-10 15:46:36 +0200825 goto out;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100826
Willy Tarreau83749182007-04-15 20:56:27 +0200827 out_wakeup:
Willy Tarreaufdccded2008-08-29 18:19:04 +0200828 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200829 out:
830 return;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200831}
832
Willy Tarreaubaaee002006-06-26 02:48:02 +0200833/*
Willy Tarreauf3c69202006-07-09 16:42:34 +0200834 * This function is used only for server health-checks. It handles the server's
Hervé COMMOWICK8776f1b2010-10-18 15:58:36 +0200835 * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
Simon Horman4a741432013-02-23 15:35:38 +0900836 * set_server_check_status() to update check->status, check->duration
837 * and check->result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200838
839 * The set_server_check_status function is called with HCHK_STATUS_L7OKD if
840 * an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
841 * returns 2xx, HCHK_STATUS_L6OK if an SSL server returns at least 5 bytes in
842 * response to an SSL HELLO (the principle is that this is enough to
843 * distinguish between an SSL server and a pure TCP relay). All other cases will
844 * call it with a proper error status like HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP,
845 * etc.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100846 *
847 * Please do NOT place any return statement in this function and only leave
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200848 * via the out label.
849 *
850 * This must be called with the server lock held.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200851 */
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200852static void __event_srv_chk_r(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200853{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200854 struct connection *conn = cs->conn;
855 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900856 struct server *s = check->server;
857 struct task *t = check->task;
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200858 char *desc;
Willy Tarreau03938182010-03-17 21:52:07 +0100859 int done;
Willy Tarreau83749182007-04-15 20:56:27 +0200860
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100861 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau83749182007-04-15 20:56:27 +0200862 goto out_wakeup;
Willy Tarreau83749182007-04-15 20:56:27 +0200863
Willy Tarreauc09572f2017-10-04 11:58:22 +0200864 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100865 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200866 goto out;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200867
Willy Tarreau83749182007-04-15 20:56:27 +0200868 /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available
869 * but the connection was closed on the remote end. Fortunately, recv still
870 * works correctly and we don't need to do the getsockopt() on linux.
871 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000872
873 /* Set buffer to point to the end of the data already read, and check
874 * that there is free space remaining. If the buffer is full, proceed
875 * with running the checks without attempting another socket read.
876 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000877
Willy Tarreau03938182010-03-17 21:52:07 +0100878 done = 0;
Nick Chalk57b1bf72010-03-16 15:50:46 +0000879
Olivier Houchard511efea2018-08-16 15:30:32 +0200880 cs->conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200881 if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
Willy Tarreau03938182010-03-17 21:52:07 +0100882 done = 1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200883 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
Willy Tarreauf1503172012-09-28 19:39:36 +0200884 /* Report network errors only if we got no other data. Otherwise
885 * we'll let the upper layers decide whether the response is OK
886 * or not. It is very common that an RST sent by the server is
887 * reported as an error just after the last data chunk.
888 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200889 chk_report_conn_err(check, errno, 0);
Willy Tarreauc1a07962010-03-16 20:55:43 +0100890 goto out_wakeup;
891 }
Willy Tarreaubaaee002006-06-26 02:48:02 +0200892 }
893
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200894 /* the rest of the code below expects the connection to be ready! */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100895 if (conn->flags & CO_FL_WAIT_XPRT && !done)
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200896 goto wait_more_data;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100897
Willy Tarreau03938182010-03-17 21:52:07 +0100898 /* Intermediate or complete response received.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200899 * Terminate string in b_head(&check->bi) buffer.
Willy Tarreau03938182010-03-17 21:52:07 +0100900 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200901 if (b_data(&check->bi) < b_size(&check->bi))
902 b_head(&check->bi)[b_data(&check->bi)] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100903 else {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200904 b_head(&check->bi)[b_data(&check->bi) - 1] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100905 done = 1; /* buffer full, don't wait for more data */
906 }
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200907
Nick Chalk57b1bf72010-03-16 15:50:46 +0000908 /* Run the checks... */
Simon Horman4a741432013-02-23 15:35:38 +0900909 switch (check->type) {
Willy Tarreau1620ec32011-08-06 17:05:02 +0200910 case PR_O2_HTTP_CHK:
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200911 if (!done && b_data(&check->bi) < strlen("HTTP/1.0 000\r"))
Willy Tarreau03938182010-03-17 21:52:07 +0100912 goto wait_more_data;
913
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100914 /* Check if the server speaks HTTP 1.X */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200915 if ((b_data(&check->bi) < strlen("HTTP/1.0 000\r")) ||
916 (memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
917 (*(b_head(&check->bi) + 12) != ' ' && *(b_head(&check->bi) + 12) != '\r')) ||
918 !isdigit((unsigned char) *(b_head(&check->bi) + 9)) || !isdigit((unsigned char) *(b_head(&check->bi) + 10)) ||
919 !isdigit((unsigned char) *(b_head(&check->bi) + 11))) {
920 cut_crlf(b_head(&check->bi));
921 set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200922
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100923 goto out_wakeup;
924 }
925
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200926 check->code = str2uic(b_head(&check->bi) + 9);
927 desc = ltrim(b_head(&check->bi) + 12, ' ');
Christopher Fauletc2a89a62017-10-23 15:54:24 +0200928
Willy Tarreaubd741542010-03-16 18:46:54 +0100929 if ((s->proxy->options & PR_O_DISABLE404) &&
Emeric Brun52a91d32017-08-31 14:41:55 +0200930 (s->next_state != SRV_ST_STOPPED) && (check->code == 404)) {
Nick Chalk57b1bf72010-03-16 15:50:46 +0000931 /* 404 may be accepted as "stopping" only if the server was up */
932 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900933 set_server_check_status(check, HCHK_STATUS_L7OKCD, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000934 }
Willy Tarreaubd741542010-03-16 18:46:54 +0100935 else if (s->proxy->options2 & PR_O2_EXP_TYPE) {
936 /* Run content verification check... We know we have at least 13 chars */
937 if (!httpchk_expect(s, done))
938 goto wait_more_data;
939 }
940 /* check the reply : HTTP/1.X 2xx and 3xx are OK */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200941 else if (*(b_head(&check->bi) + 9) == '2' || *(b_head(&check->bi) + 9) == '3') {
Willy Tarreaubd741542010-03-16 18:46:54 +0100942 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900943 set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
Willy Tarreaubd741542010-03-16 18:46:54 +0100944 }
Nick Chalk57b1bf72010-03-16 15:50:46 +0000945 else {
946 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900947 set_server_check_status(check, HCHK_STATUS_L7STS, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000948 }
Willy Tarreau1620ec32011-08-06 17:05:02 +0200949 break;
950
Simon Hormana2b9dad2013-02-12 10:45:54 +0900951 case PR_O2_LB_AGENT_CHK: {
Willy Tarreau81f5d942013-12-09 20:51:51 +0100952 int status = HCHK_STATUS_CHECKED;
953 const char *hs = NULL; /* health status */
954 const char *as = NULL; /* admin status */
955 const char *ps = NULL; /* performance status */
Nenad Merdanovic174dd372016-04-24 23:10:06 +0200956 const char *cs = NULL; /* maxconn */
Willy Tarreau81f5d942013-12-09 20:51:51 +0100957 const char *err = NULL; /* first error to report */
958 const char *wrn = NULL; /* first warning to report */
959 char *cmd, *p;
Simon Hormana2b9dad2013-02-12 10:45:54 +0900960
Willy Tarreau81f5d942013-12-09 20:51:51 +0100961 /* We're getting an agent check response. The agent could
962 * have been disabled in the mean time with a long check
963 * still pending. It is important that we ignore the whole
964 * response.
965 */
966 if (!(check->server->agent.state & CHK_ST_ENABLED))
967 break;
968
969 /* The agent supports strings made of a single line ended by the
970 * first CR ('\r') or LF ('\n'). This line is composed of words
971 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
972 * line may optionally contained a description of a state change
973 * after a sharp ('#'), which is only considered if a health state
974 * is announced.
975 *
976 * Words may be composed of :
977 * - a numeric weight suffixed by the percent character ('%').
978 * - a health status among "up", "down", "stopped", and "fail".
979 * - an admin status among "ready", "drain", "maint".
980 *
981 * These words may appear in any order. If multiple words of the
982 * same category appear, the last one wins.
983 */
984
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200985 p = b_head(&check->bi);
Willy Tarreau9809b782013-12-11 21:40:11 +0100986 while (*p && *p != '\n' && *p != '\r')
987 p++;
988
989 if (!*p) {
990 if (!done)
991 goto wait_more_data;
Simon Hormana2b9dad2013-02-12 10:45:54 +0900992
Willy Tarreau9809b782013-12-11 21:40:11 +0100993 /* at least inform the admin that the agent is mis-behaving */
994 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
995 break;
996 }
Willy Tarreau81f5d942013-12-09 20:51:51 +0100997
Willy Tarreau9809b782013-12-11 21:40:11 +0100998 *p = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200999 cmd = b_head(&check->bi);
Simon Hormana2b9dad2013-02-12 10:45:54 +09001000
Willy Tarreau81f5d942013-12-09 20:51:51 +01001001 while (*cmd) {
1002 /* look for next word */
1003 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1004 cmd++;
1005 continue;
1006 }
Simon Horman671b6f02013-11-25 10:46:39 +09001007
Willy Tarreau81f5d942013-12-09 20:51:51 +01001008 if (*cmd == '#') {
1009 /* this is the beginning of a health status description,
1010 * skip the sharp and blanks.
1011 */
1012 cmd++;
1013 while (*cmd == '\t' || *cmd == ' ')
1014 cmd++;
Simon Horman671b6f02013-11-25 10:46:39 +09001015 break;
Simon Hormana2b9dad2013-02-12 10:45:54 +09001016 }
Willy Tarreau81f5d942013-12-09 20:51:51 +01001017
1018 /* find the end of the word so that we have a null-terminated
1019 * word between <cmd> and <p>.
1020 */
1021 p = cmd + 1;
1022 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1023 p++;
1024 if (*p)
1025 *p++ = 0;
1026
1027 /* first, health statuses */
1028 if (strcasecmp(cmd, "up") == 0) {
1029 check->health = check->rise + check->fall - 1;
Simon Hormana2b9dad2013-02-12 10:45:54 +09001030 status = HCHK_STATUS_L7OKD;
Willy Tarreau81f5d942013-12-09 20:51:51 +01001031 hs = cmd;
1032 }
1033 else if (strcasecmp(cmd, "down") == 0) {
1034 check->health = 0;
1035 status = HCHK_STATUS_L7STS;
1036 hs = cmd;
Simon Hormana2b9dad2013-02-12 10:45:54 +09001037 }
Willy Tarreau81f5d942013-12-09 20:51:51 +01001038 else if (strcasecmp(cmd, "stopped") == 0) {
1039 check->health = 0;
1040 status = HCHK_STATUS_L7STS;
1041 hs = cmd;
1042 }
1043 else if (strcasecmp(cmd, "fail") == 0) {
1044 check->health = 0;
1045 status = HCHK_STATUS_L7STS;
1046 hs = cmd;
1047 }
1048 /* admin statuses */
1049 else if (strcasecmp(cmd, "ready") == 0) {
1050 as = cmd;
1051 }
1052 else if (strcasecmp(cmd, "drain") == 0) {
1053 as = cmd;
1054 }
1055 else if (strcasecmp(cmd, "maint") == 0) {
1056 as = cmd;
1057 }
Nenad Merdanovic174dd372016-04-24 23:10:06 +02001058 /* try to parse a weight here and keep the last one */
Willy Tarreau81f5d942013-12-09 20:51:51 +01001059 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1060 ps = cmd;
1061 }
Nenad Merdanovic174dd372016-04-24 23:10:06 +02001062 /* try to parse a maxconn here */
1063 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1064 cs = cmd;
1065 }
Willy Tarreau81f5d942013-12-09 20:51:51 +01001066 else {
1067 /* keep a copy of the first error */
1068 if (!err)
1069 err = cmd;
1070 }
1071 /* skip to next word */
1072 cmd = p;
1073 }
1074 /* here, cmd points either to \0 or to the beginning of a
1075 * description. Skip possible leading spaces.
1076 */
1077 while (*cmd == ' ' || *cmd == '\n')
1078 cmd++;
1079
1080 /* First, update the admin status so that we avoid sending other
1081 * possibly useless warnings and can also update the health if
1082 * present after going back up.
1083 */
1084 if (as) {
1085 if (strcasecmp(as, "drain") == 0)
1086 srv_adm_set_drain(check->server);
1087 else if (strcasecmp(as, "maint") == 0)
1088 srv_adm_set_maint(check->server);
1089 else
1090 srv_adm_set_ready(check->server);
Simon Hormana2b9dad2013-02-12 10:45:54 +09001091 }
1092
Willy Tarreau81f5d942013-12-09 20:51:51 +01001093 /* now change weights */
1094 if (ps) {
1095 const char *msg;
1096
1097 msg = server_parse_weight_change_request(s, ps);
1098 if (!wrn || !*wrn)
1099 wrn = msg;
1100 }
1101
Nenad Merdanovic174dd372016-04-24 23:10:06 +02001102 if (cs) {
1103 const char *msg;
1104
1105 cs += strlen("maxconn:");
1106
1107 msg = server_parse_maxconn_change_request(s, cs);
1108 if (!wrn || !*wrn)
1109 wrn = msg;
1110 }
1111
Willy Tarreau81f5d942013-12-09 20:51:51 +01001112 /* and finally health status */
1113 if (hs) {
1114 /* We'll report some of the warnings and errors we have
1115 * here. Down reports are critical, we leave them untouched.
1116 * Lack of report, or report of 'UP' leaves the room for
1117 * ERR first, then WARN.
Simon Hormana2b9dad2013-02-12 10:45:54 +09001118 */
Willy Tarreau81f5d942013-12-09 20:51:51 +01001119 const char *msg = cmd;
Willy Tarreau83061a82018-07-13 11:56:34 +02001120 struct buffer *t;
Willy Tarreau81f5d942013-12-09 20:51:51 +01001121
1122 if (!*msg || status == HCHK_STATUS_L7OKD) {
1123 if (err && *err)
1124 msg = err;
1125 else if (wrn && *wrn)
1126 msg = wrn;
Simon Hormana2b9dad2013-02-12 10:45:54 +09001127 }
Willy Tarreau81f5d942013-12-09 20:51:51 +01001128
1129 t = get_trash_chunk();
1130 chunk_printf(t, "via agent : %s%s%s%s",
1131 hs, *msg ? " (" : "",
1132 msg, *msg ? ")" : "");
1133
Willy Tarreau843b7cb2018-07-13 10:54:26 +02001134 set_server_check_status(check, status, t->area);
Simon Hormana2b9dad2013-02-12 10:45:54 +09001135 }
Willy Tarreau81f5d942013-12-09 20:51:51 +01001136 else if (err && *err) {
1137 /* No status change but we'd like to report something odd.
1138 * Just report the current state and copy the message.
1139 */
1140 chunk_printf(&trash, "agent reports an error : %s", err);
Willy Tarreau843b7cb2018-07-13 10:54:26 +02001141 set_server_check_status(check, status/*check->status*/,
1142 trash.area);
Simon Hormana2b9dad2013-02-12 10:45:54 +09001143
Willy Tarreau81f5d942013-12-09 20:51:51 +01001144 }
1145 else if (wrn && *wrn) {
1146 /* No status change but we'd like to report something odd.
1147 * Just report the current state and copy the message.
1148 */
1149 chunk_printf(&trash, "agent warns : %s", wrn);
Willy Tarreau843b7cb2018-07-13 10:54:26 +02001150 set_server_check_status(check, status/*check->status*/,
1151 trash.area);
Willy Tarreau81f5d942013-12-09 20:51:51 +01001152 }
1153 else
1154 set_server_check_status(check, status, NULL);
Simon Hormana2b9dad2013-02-12 10:45:54 +09001155 break;
1156 }
1157
Christopher Fauletba7bc162016-11-07 21:07:38 +01001158 case PR_O2_SPOP_CHK: {
1159 unsigned int framesz;
1160 char err[HCHK_DESC_LEN];
1161
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001162 if (!done && b_data(&check->bi) < 4)
Christopher Fauletba7bc162016-11-07 21:07:38 +01001163 goto wait_more_data;
1164
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001165 memcpy(&framesz, b_head(&check->bi), 4);
Christopher Fauletba7bc162016-11-07 21:07:38 +01001166 framesz = ntohl(framesz);
1167
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001168 if (!done && b_data(&check->bi) < (4+framesz))
Christopher Fauletba7bc162016-11-07 21:07:38 +01001169 goto wait_more_data;
1170
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001171 if (!spoe_handle_healthcheck_response(b_head(&check->bi)+4, framesz, err, HCHK_DESC_LEN-1))
Christopher Fauletba7bc162016-11-07 21:07:38 +01001172 set_server_check_status(check, HCHK_STATUS_L7OKD, "SPOA server is ok");
1173 else
1174 set_server_check_status(check, HCHK_STATUS_L7STS, err);
1175 break;
1176 }
1177
Willy Tarreau1620ec32011-08-06 17:05:02 +02001178 default:
Willy Tarreau4c1a2b32019-09-05 18:43:22 +02001179 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001180 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001181 if (check->use_ssl == 1)
Willy Tarreau4c1a2b32019-09-05 18:43:22 +02001182 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
1183 else
1184 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
1185 }
Willy Tarreau1620ec32011-08-06 17:05:02 +02001186 break;
1187 } /* switch */
Willy Tarreau83749182007-04-15 20:56:27 +02001188
Willy Tarreauc7dd71a2007-11-30 08:33:21 +01001189 out_wakeup:
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001190 /* collect possible new errors */
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001191 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001192 chk_report_conn_err(check, 0, 0);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001193
Nick Chalk57b1bf72010-03-16 15:50:46 +00001194 /* Reset the check buffer... */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001195 *b_head(&check->bi) = '\0';
1196 b_reset(&check->bi);
Nick Chalk57b1bf72010-03-16 15:50:46 +00001197
Steven Davidovitz544d4812017-03-08 11:06:20 -08001198 /* Close the connection... We still attempt to nicely close if,
1199 * for instance, SSL needs to send a "close notify." Later, we perform
1200 * a hard close and reset the connection if some data are pending,
1201 * otherwise we end up with many TIME_WAITs and eat all the source port
1202 * range quickly. To avoid sending RSTs all the time, we first try to
1203 * drain pending data.
Willy Tarreaufd29cc52012-11-23 09:18:20 +01001204 */
Olivier Houchard6c7e96a2019-07-02 16:35:18 +02001205 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
1206 * connection, to make sure cs_shutw() will not lead to a shutdown()
1207 * that would provoke TIME_WAITs.
1208 */
1209 cs_shutr(cs, CS_SHR_DRAIN);
Willy Tarreauecdb3fe2017-10-05 15:25:48 +02001210 cs_shutw(cs, CS_SHW_NORMAL);
Willy Tarreau2b57cb82013-06-10 19:56:38 +02001211
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001212 /* OK, let's not stay here forever */
Willy Tarreau6aaa1b82013-12-11 17:09:34 +01001213 if (check->result == CHK_RES_FAILED)
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001214 conn->flags |= CO_FL_ERROR;
1215
Willy Tarreaufdccded2008-08-29 18:19:04 +02001216 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchardaf4021e2018-08-09 13:06:55 +02001217out:
Willy Tarreau3267d362012-08-17 23:53:56 +02001218 return;
Willy Tarreau03938182010-03-17 21:52:07 +01001219
1220 wait_more_data:
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001221 cs->conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Olivier Houchardaf4021e2018-08-09 13:06:55 +02001222 goto out;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001223}
1224
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001225/*
1226 * This function is used only for server health-checks. It handles connection
1227 * status updates including errors. If necessary, it wakes the check task up.
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001228 * It returns 0 on normal cases, <0 if at least one close() has happened on the
1229 * connection (eg: reconnect).
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001230 */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001231static int wake_srv_chk(struct conn_stream *cs)
Willy Tarreau20bea422012-07-06 12:00:49 +02001232{
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001233 struct connection *conn = cs->conn;
1234 struct check *check = cs->data;
Olivier Houchard0923fa42019-01-11 18:43:04 +01001235 struct email_alertq *q = container_of(check, typeof(*q), check);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001236 int ret = 0;
Willy Tarreau20bea422012-07-06 12:00:49 +02001237
Olivier Houchard0923fa42019-01-11 18:43:04 +01001238 if (check->server)
1239 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
1240 else
1241 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001242
Willy Tarreauc09572f2017-10-04 11:58:22 +02001243 /* we may have to make progress on the TCP checks */
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001244 if (check->type == PR_O2_TCPCHK_CHK) {
1245 ret = tcpcheck_main(check);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001246 cs = check->cs;
Willy Tarreau543abd42018-09-20 11:25:12 +02001247 conn = cs->conn;
Willy Tarreauc5940392019-09-05 17:38:40 +02001248 } else {
1249 if (!(check->wait_list.events & SUB_RETRY_SEND))
1250 __event_srv_chk_w(cs);
1251 if (!(check->wait_list.events & SUB_RETRY_RECV))
1252 __event_srv_chk_r(cs);
1253 }
Willy Tarreauc09572f2017-10-04 11:58:22 +02001254
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001255 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
Willy Tarreau02b0f582013-12-03 15:42:33 +01001256 /* We may get error reports bypassing the I/O handlers, typically
1257 * the case when sending a pure TCP check which fails, then the I/O
1258 * handlers above are not called. This is completely handled by the
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001259 * main processing task so let's simply wake it up. If we get here,
1260 * we expect errno to still be valid.
1261 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001262 chk_report_conn_err(check, errno, 0);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001263 task_wakeup(check->task, TASK_WOKEN_IO);
1264 }
Willy Tarreau911db9b2020-01-23 16:27:54 +01001265 else if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Willy Tarreau3be293f2014-02-05 18:31:24 +01001266 /* we may get here if only a connection probe was required : we
1267 * don't have any data to send nor anything expected in response,
1268 * so the completion of the connection establishment is enough.
1269 */
1270 task_wakeup(check->task, TASK_WOKEN_IO);
1271 }
Willy Tarreau2d351b62013-12-05 02:36:25 +01001272
Willy Tarreau6aaa1b82013-12-11 17:09:34 +01001273 if (check->result != CHK_RES_UNKNOWN) {
Christopher Faulet774c4862019-01-21 14:15:50 +01001274 /* Check complete or aborted. If connection not yet closed do it
1275 * now and wake the check task up to be sure the result is
1276 * handled ASAP. */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001277 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001278 cs_close(cs);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001279 ret = -1;
Olivier Houchardf4949572019-07-02 17:42:22 +02001280 /* We may have been scheduled to run, and the
1281 * I/O handler expects to have a cs, so remove
1282 * the tasklet
1283 */
1284 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Christopher Faulet774c4862019-01-21 14:15:50 +01001285 task_wakeup(check->task, TASK_WOKEN_IO);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001286 }
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001287
Olivier Houchard0923fa42019-01-11 18:43:04 +01001288 if (check->server)
1289 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
1290 else
1291 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001292
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001293 /* if a connection got replaced, we must absolutely prevent the connection
1294 * handler from touching its fd, and perform the FD polling updates ourselves
1295 */
1296 if (ret < 0)
1297 conn_cond_update_polling(conn);
1298
1299 return ret;
Willy Tarreau20bea422012-07-06 12:00:49 +02001300}
1301
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001302struct data_cb check_conn_cb = {
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001303 .wake = wake_srv_chk,
Willy Tarreau8e0bb0a2016-11-24 16:58:12 +01001304 .name = "CHCK",
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001305};
1306
Willy Tarreaubaaee002006-06-26 02:48:02 +02001307/*
Willy Tarreau2e993902011-10-31 11:53:20 +01001308 * updates the server's weight during a warmup stage. Once the final weight is
1309 * reached, the task automatically stops. Note that any server status change
1310 * must have updated s->last_change accordingly.
1311 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001312static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Willy Tarreau2e993902011-10-31 11:53:20 +01001313{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001314 struct server *s = context;
Willy Tarreau2e993902011-10-31 11:53:20 +01001315
1316 /* by default, plan on stopping the task */
1317 t->expire = TICK_ETERNITY;
Emeric Brun52a91d32017-08-31 14:41:55 +02001318 if ((s->next_admin & SRV_ADMF_MAINT) ||
1319 (s->next_state != SRV_ST_STARTING))
Willy Tarreau2e993902011-10-31 11:53:20 +01001320 return t;
1321
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001322 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
1323
Willy Tarreau892337c2014-05-13 23:41:20 +02001324 /* recalculate the weights and update the state */
Willy Tarreau3ff577e2018-08-02 11:48:52 +02001325 server_recalc_eweight(s, 1);
Willy Tarreau2e993902011-10-31 11:53:20 +01001326
1327 /* probably that we can refill this server with a bit more connections */
Willy Tarreau4aac7db2014-05-16 11:48:10 +02001328 pendconn_grab_from_px(s);
Willy Tarreau2e993902011-10-31 11:53:20 +01001329
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001330 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
1331
Willy Tarreau2e993902011-10-31 11:53:20 +01001332 /* get back there in 1 second or 1/20th of the slowstart interval,
1333 * whichever is greater, resulting in small 5% steps.
1334 */
Emeric Brun52a91d32017-08-31 14:41:55 +02001335 if (s->next_state == SRV_ST_STARTING)
Willy Tarreau2e993902011-10-31 11:53:20 +01001336 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
1337 return t;
1338}
1339
Willy Tarreau894c6422017-10-04 15:58:52 +02001340/* returns the first NON-COMMENT tcp-check rule from list <list> or NULL if
1341 * none was found.
1342 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001343static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Willy Tarreau894c6422017-10-04 15:58:52 +02001344{
1345 struct tcpcheck_rule *r;
1346
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001347 list_for_each_entry(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001348 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Willy Tarreau894c6422017-10-04 15:58:52 +02001349 return r;
1350 }
1351 return NULL;
1352}
1353
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001354/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1355 * NULL if non was found. If <start> is NULL, it relies on
1356 * get_first_tcpcheck_rule().
1357 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001358static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001359{
1360 struct tcpcheck_rule *r;
1361
1362 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001363 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001364
1365 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001366 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001367 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001368 return r;
1369 }
1370 return NULL;
1371}
1372
Willy Tarreau2e993902011-10-31 11:53:20 +01001373/*
Simon Horman98637e52014-06-20 12:30:16 +09001374 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001375 *
1376 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001377 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1378 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1379 * - SF_ERR_SRVTO if there are no more servers
1380 * - SF_ERR_SRVCL if the connection was refused by the server
1381 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1382 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1383 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001384 * - SF_ERR_CHK_PORT if no port could be found to run a health check on an AF_INET* socket
Tim Düsterhus4896c442016-11-29 02:15:19 +01001385 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001386 * Note that we try to prevent the network stack from sending the ACK during the
1387 * connect() when a pure TCP check is used (without PROXY protocol).
1388 */
Simon Horman98637e52014-06-20 12:30:16 +09001389static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001390{
1391 struct check *check = t->context;
1392 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001393 struct conn_stream *cs = check->cs;
1394 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001395 struct protocol *proto;
1396 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001397 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001398
Willy Tarreau00149122017-10-04 18:05:01 +02001399 /* we cannot have a connection here */
1400 if (conn)
1401 return SF_ERR_INTERNAL;
1402
Simon Hormanb00d17a2014-06-13 16:18:16 +09001403 /* prepare the check buffer.
1404 * This should not be used if check is the secondary agent check
1405 * of a server as s->proxy->check_req will relate to the
1406 * configuration of the primary check. Similarly, tcp-check uses
1407 * its own strings.
1408 */
1409 if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001410 b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001411
Christopher Faulet811f78c2020-04-01 11:10:27 +02001412 /* we want to check if this host replies to HTTP requests
Simon Hormanb00d17a2014-06-13 16:18:16 +09001413 * so we'll send the request, and won't wake the checker up now.
1414 */
Christopher Faulet811f78c2020-04-01 11:10:27 +02001415 if ((check->type) == PR_O2_HTTP_CHK) {
Cyril Bonté32602d22015-01-30 00:07:07 +01001416 /* prevent HTTP keep-alive when "http-check expect" is used */
1417 if (s->proxy->options2 & PR_O2_EXP_TYPE)
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001418 b_putist(&check->bo, ist("Connection: close\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001419
1420 /* If there is a body, add its content-length */
1421 if (s->proxy->check_body_len)
1422 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
1423
1424 /* Add configured headers */
1425 if (s->proxy->check_hdrs)
1426 b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
1427
1428 /* Add send-state header */
1429 if (s->proxy->options2 & PR_O2_CHK_SNDST)
1430 b_putblk(&check->bo, trash.area,
1431 httpchk_build_status_header(s, trash.area, trash.size));
1432
1433 /* end-of-header */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001434 b_putist(&check->bo, ist("\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001435
1436 /* Add the body */
1437 if (s->proxy->check_body)
1438 b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
1439
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001440 *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
Simon Hormanb00d17a2014-06-13 16:18:16 +09001441 }
1442 }
1443
James Brown55f9ff12015-10-21 18:19:05 -07001444 if ((check->type & PR_O2_LB_AGENT_CHK) && check->send_string_len) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001445 b_putblk(&check->bo, check->send_string, check->send_string_len);
James Brown55f9ff12015-10-21 18:19:05 -07001446 }
1447
Willy Tarreauf411cce2017-10-04 16:21:19 +02001448 /* for tcp-checks, the initial connection setup is handled separately as
1449 * it may be sent to a specific port and not to the server's.
1450 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001451 if (check->type == PR_O2_TCPCHK_CHK) {
1452 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001453 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001454 tcpcheck_main(check);
1455 return SF_ERR_UP;
1456 }
1457
Simon Hormanb00d17a2014-06-13 16:18:16 +09001458 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001459 cs = check->cs = cs_new(NULL);
1460 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001461 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001462 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001463 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001464 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001465 tasklet_set_tid(check->wait_list.tasklet, tid);
1466
Simon Hormanb00d17a2014-06-13 16:18:16 +09001467
Willy Tarreauca79f592019-07-17 19:04:47 +02001468 if (!sockaddr_alloc(&conn->dst))
1469 return SF_ERR_RESOURCE;
1470
Simon Horman41f58762015-01-30 11:22:56 +09001471 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001472 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001473 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001474 }
1475 else {
1476 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001477 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001478 }
1479
Alexander Liu2a54bb72019-05-22 19:44:48 +08001480 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1481 conn->send_proxy_ofs = 1;
1482 conn->flags |= CO_FL_SOCKS4;
1483 }
1484
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001485 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001486 conn->target = &s->obj_type;
1487
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001488 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001489 int i = 0;
1490
1491 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001492 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001493 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001494
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001495 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001496 }
1497
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001498 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001499
Willy Tarreaube373152018-09-06 11:45:30 +02001500 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001501 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1502 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001503 cs_attach(cs, check, &check_conn_cb);
1504
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001505 /* only plain tcp check supports quick ACK */
1506 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001507
Willy Tarreaue7dff022015-04-03 01:14:29 +02001508 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001509 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001510 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001511
Willy Tarreau16257f62017-11-02 15:45:00 +01001512
Olivier Houchard9130a962017-10-17 17:33:43 +02001513#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001514 if (ret == SF_ERR_NONE) {
1515 if (s->check.sni)
1516 ssl_sock_set_servername(conn, s->check.sni);
1517 if (s->check.alpn_str)
1518 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1519 s->check.alpn_len);
1520 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001521#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001522 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001523 conn->send_proxy_ofs = 1;
1524 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001525 }
1526 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1527 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001528 if (xprt_add_hs(conn) < 0)
1529 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001530 }
1531
1532 return ret;
1533}
1534
Simon Horman98637e52014-06-20 12:30:16 +09001535static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001536static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001537__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001538
1539void block_sigchld(void)
1540{
1541 sigset_t set;
1542 sigemptyset(&set);
1543 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001544 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001545}
1546
1547void unblock_sigchld(void)
1548{
1549 sigset_t set;
1550 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001551 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001552 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001553}
1554
Simon Horman98637e52014-06-20 12:30:16 +09001555static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1556{
1557 struct pid_list *elem;
1558 struct check *check = t->context;
1559
Willy Tarreaubafbe012017-11-24 17:34:44 +01001560 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001561 if (!elem)
1562 return NULL;
1563 elem->pid = pid;
1564 elem->t = t;
1565 elem->exited = 0;
1566 check->curpid = elem;
1567 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001568
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001569 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001570 LIST_ADD(&pid_list, &elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001571 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001572
Simon Horman98637e52014-06-20 12:30:16 +09001573 return elem;
1574}
1575
Simon Horman98637e52014-06-20 12:30:16 +09001576static void pid_list_del(struct pid_list *elem)
1577{
1578 struct check *check;
1579
1580 if (!elem)
1581 return;
1582
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001583 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001584 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001585 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001586
Simon Horman98637e52014-06-20 12:30:16 +09001587 if (!elem->exited)
1588 kill(elem->pid, SIGTERM);
1589
1590 check = elem->t->context;
1591 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001592 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001593}
1594
1595/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1596static void pid_list_expire(pid_t pid, int status)
1597{
1598 struct pid_list *elem;
1599
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001600 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001601 list_for_each_entry(elem, &pid_list, list) {
1602 if (elem->pid == pid) {
1603 elem->t->expire = now_ms;
1604 elem->status = status;
1605 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001606 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001607 break;
Simon Horman98637e52014-06-20 12:30:16 +09001608 }
1609 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001610 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001611}
1612
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001613static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001614{
1615 pid_t pid;
1616 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001617
Simon Horman98637e52014-06-20 12:30:16 +09001618 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1619 pid_list_expire(pid, status);
1620}
1621
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001622static int init_pid_list(void)
1623{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001624 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001625 /* Nothing to do */
1626 return 0;
1627
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001628 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001629 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1630 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001631 return 1;
1632 }
1633
Willy Tarreaubafbe012017-11-24 17:34:44 +01001634 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1635 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001636 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1637 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001638 return 1;
1639 }
1640
1641 return 0;
1642}
1643
Cyril Bontéac92a062014-12-27 22:28:38 +01001644/* helper macro to set an environment variable and jump to a specific label on failure. */
1645#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001646
1647/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001648 * helper function to allocate enough memory to store an environment variable.
1649 * It will also check that the environment variable is updatable, and silently
1650 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001651 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001652static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001653{
1654 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001655 char *envname;
1656 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001657
Cyril Bontéac92a062014-12-27 22:28:38 +01001658 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001659 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001660 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001661 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001662
1663 envname = extcheck_envs[idx].name;
1664 vmaxlen = extcheck_envs[idx].vmaxlen;
1665
1666 /* Check if the environment variable is already set, and silently reject
1667 * the update if this one is not updatable. */
1668 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1669 return 0;
1670
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001671 /* Instead of sending NOT_USED, sending an empty value is preferable */
1672 if (strcmp(value, "NOT_USED") == 0) {
1673 value = "";
1674 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001675
1676 len = strlen(envname) + 1;
1677 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1678 len += strlen(value);
1679 else
1680 len += vmaxlen;
1681
1682 if (!check->envp[idx])
1683 check->envp[idx] = malloc(len + 1);
1684
1685 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001686 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001687 return 1;
1688 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001689 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001690 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001691 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001692 return 1;
1693 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001694 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001695 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001696 return 1;
1697 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001698 return 0;
1699}
Simon Horman98637e52014-06-20 12:30:16 +09001700
1701static int prepare_external_check(struct check *check)
1702{
1703 struct server *s = check->server;
1704 struct proxy *px = s->proxy;
1705 struct listener *listener = NULL, *l;
1706 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001707 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001708 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001709
1710 list_for_each_entry(l, &px->conf.listeners, by_fe)
1711 /* Use the first INET, INET6 or UNIX listener */
1712 if (l->addr.ss_family == AF_INET ||
1713 l->addr.ss_family == AF_INET6 ||
1714 l->addr.ss_family == AF_UNIX) {
1715 listener = l;
1716 break;
1717 }
1718
Simon Horman98637e52014-06-20 12:30:16 +09001719 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001720 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1721 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001722 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001723 goto err;
1724 }
Simon Horman98637e52014-06-20 12:30:16 +09001725
Cyril Bontéac92a062014-12-27 22:28:38 +01001726 check->argv = calloc(6, sizeof(char *));
1727 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001728 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001729 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001730 }
Simon Horman98637e52014-06-20 12:30:16 +09001731
1732 check->argv[0] = px->check_command;
1733
Cyril Bonté777be862014-12-02 21:21:35 +01001734 if (!listener) {
1735 check->argv[1] = strdup("NOT_USED");
1736 check->argv[2] = strdup("NOT_USED");
1737 }
1738 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001739 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001740 addr_to_str(&listener->addr, buf, sizeof(buf));
1741 check->argv[1] = strdup(buf);
1742 port_to_str(&listener->addr, buf, sizeof(buf));
1743 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001744 }
1745 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001746 const struct sockaddr_un *un;
1747
1748 un = (struct sockaddr_un *)&listener->addr;
1749 check->argv[1] = strdup(un->sun_path);
1750 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001751 }
1752 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001753 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001754 goto err;
1755 }
1756
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001757 if (!check->argv[1] || !check->argv[2]) {
1758 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1759 goto err;
1760 }
1761
1762 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1763 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1764 if (!check->argv[3] || !check->argv[4]) {
1765 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1766 goto err;
1767 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001768
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001769 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001770 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001771 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001772
Cyril Bontéac92a062014-12-27 22:28:38 +01001773 for (i = 0; i < 5; i++) {
1774 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001775 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001776 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001777 }
1778 }
Simon Horman98637e52014-06-20 12:30:16 +09001779
Cyril Bontéac92a062014-12-27 22:28:38 +01001780 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001781 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001782 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1783 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1784 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1785 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001786 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001787 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1788 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1789 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1790 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1791 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1792 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1793
1794 /* Ensure that we don't leave any hole in check->envp */
1795 for (i = 0; i < EXTCHK_SIZE; i++)
1796 if (!check->envp[i])
1797 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001798
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001799 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001800err:
1801 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001802 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001803 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001804 free(check->envp);
1805 check->envp = NULL;
1806 }
1807
1808 if (check->argv) {
1809 for (i = 1; i < 5; i++)
1810 free(check->argv[i]);
1811 free(check->argv);
1812 check->argv = NULL;
1813 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001814 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001815}
1816
Simon Hormanb00d17a2014-06-13 16:18:16 +09001817/*
Simon Horman98637e52014-06-20 12:30:16 +09001818 * establish a server health-check that makes use of a process.
1819 *
1820 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001821 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001822 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001823 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001824 *
1825 * Blocks and then unblocks SIGCHLD
1826 */
1827static int connect_proc_chk(struct task *t)
1828{
Cyril Bontéac92a062014-12-27 22:28:38 +01001829 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001830 struct check *check = t->context;
1831 struct server *s = check->server;
1832 struct proxy *px = s->proxy;
1833 int status;
1834 pid_t pid;
1835
Willy Tarreaue7dff022015-04-03 01:14:29 +02001836 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001837
1838 block_sigchld();
1839
1840 pid = fork();
1841 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001842 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1843 (global.tune.options & GTUNE_INSECURE_FORK) ?
1844 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001845 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001846 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1847 goto out;
1848 }
1849 if (pid == 0) {
1850 /* Child */
1851 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001852 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001853 int fd;
1854
1855 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1856 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1857
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001858 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001859
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001860 /* restore the initial FD limits */
1861 limit.rlim_cur = rlim_fd_cur_at_boot;
1862 limit.rlim_max = rlim_fd_max_at_boot;
1863 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1864 getrlimit(RLIMIT_NOFILE, &limit);
1865 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1866 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1867 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1868 }
1869
Simon Horman98637e52014-06-20 12:30:16 +09001870 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001871
1872 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001873 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001874
1875 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1876 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1877
1878 *check->argv[4] = 0;
1879 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1880 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1881 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1882
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001883 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001884 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001885 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1886 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001887 exit(-1);
1888 }
1889
1890 /* Parent */
1891 if (check->result == CHK_RES_UNKNOWN) {
1892 if (pid_list_add(pid, t) != NULL) {
1893 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1894
1895 if (px->timeout.check && px->timeout.connect) {
1896 int t_con = tick_add(now_ms, px->timeout.connect);
1897 t->expire = tick_first(t->expire, t_con);
1898 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001899 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001900 goto out;
1901 }
1902 else {
1903 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1904 }
1905 kill(pid, SIGTERM); /* process creation error */
1906 }
1907 else
1908 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1909
1910out:
1911 unblock_sigchld();
1912 return status;
1913}
1914
1915/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001916 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001917 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001918 *
1919 * Please do NOT place any return statement in this function and only leave
1920 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001921 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001922static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001923{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001924 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001925 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001926 int rv;
1927 int ret;
1928 int expired = tick_is_expired(t->expire, now_ms);
1929
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001930 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001931 if (!(check->state & CHK_ST_INPROGRESS)) {
1932 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001933 if (!expired) /* woke up too early */
1934 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001935
1936 /* we don't send any health-checks when the proxy is
1937 * stopped, the server should not be checked or the check
1938 * is disabled.
1939 */
1940 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1941 s->proxy->state == PR_STSTOPPED)
1942 goto reschedule;
1943
1944 /* we'll initiate a new check */
1945 set_server_check_status(check, HCHK_STATUS_START, NULL);
1946
1947 check->state |= CHK_ST_INPROGRESS;
1948
Simon Hormandbf70192015-01-30 11:22:53 +09001949 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001950 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001951 /* the process was forked, we allow up to min(inter,
1952 * timeout.connect) for it to report its status, but
1953 * only when timeout.check is set as it may be to short
1954 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001955 */
1956 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1957
1958 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1959 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1960 t->expire = tick_first(t->expire, t_con);
1961 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001962 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001963 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001964 }
1965
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001966 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001967
1968 check->state &= ~CHK_ST_INPROGRESS;
1969 check_notify_failure(check);
1970
1971 /* we allow up to min(inter, timeout.connect) for a connection
1972 * to establish but only when timeout.check is set
1973 * as it may be to short for a full check otherwise
1974 */
1975 while (tick_is_expired(t->expire, now_ms)) {
1976 int t_con;
1977
1978 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1979 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1980
1981 if (s->proxy->timeout.check)
1982 t->expire = tick_first(t->expire, t_con);
1983 }
1984 }
1985 else {
1986 /* there was a test running.
1987 * First, let's check whether there was an uncaught error,
1988 * which can happen on connect timeout or error.
1989 */
1990 if (check->result == CHK_RES_UNKNOWN) {
1991 /* good connection is enough for pure TCP check */
1992 struct pid_list *elem = check->curpid;
1993 int status = HCHK_STATUS_UNKNOWN;
1994
1995 if (elem->exited) {
1996 status = elem->status; /* Save in case the process exits between use below */
1997 if (!WIFEXITED(status))
1998 check->code = -1;
1999 else
2000 check->code = WEXITSTATUS(status);
2001 if (!WIFEXITED(status) || WEXITSTATUS(status))
2002 status = HCHK_STATUS_PROCERR;
2003 else
2004 status = HCHK_STATUS_PROCOK;
2005 } else if (expired) {
2006 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01002007 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09002008 kill(elem->pid, SIGTERM);
2009 }
2010 set_server_check_status(check, status, NULL);
2011 }
2012
2013 if (check->result == CHK_RES_FAILED) {
2014 /* a failure or timeout detected */
2015 check_notify_failure(check);
2016 }
2017 else if (check->result == CHK_RES_CONDPASS) {
2018 /* check is OK but asks for stopping mode */
2019 check_notify_stopping(check);
2020 }
2021 else if (check->result == CHK_RES_PASSED) {
2022 /* a success was detected */
2023 check_notify_success(check);
2024 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002025 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09002026 check->state &= ~CHK_ST_INPROGRESS;
2027
2028 pid_list_del(check->curpid);
2029
2030 rv = 0;
2031 if (global.spread_checks > 0) {
2032 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002033 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09002034 }
2035 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
2036 }
2037
2038 reschedule:
2039 while (tick_is_expired(t->expire, now_ms))
2040 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002041
2042 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01002043 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09002044 return t;
2045}
2046
2047/*
2048 * manages a server health-check that uses a connection. Returns
2049 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002050 *
2051 * Please do NOT place any return statement in this function and only leave
2052 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09002053 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002054static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02002055{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002056 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002057 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002058 struct conn_stream *cs = check->cs;
2059 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002060 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02002061 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01002062 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02002063
Olivier Houchard0923fa42019-01-11 18:43:04 +01002064 if (check->server)
2065 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002066 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01002067 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002068 if (!expired) /* woke up too early */
2069 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002070
Simon Horman671b6f02013-11-25 10:46:39 +09002071 /* we don't send any health-checks when the proxy is
2072 * stopped, the server should not be checked or the check
2073 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02002074 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01002075 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002076 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01002077 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002078
2079 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09002080 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02002081
Willy Tarreau2c115e52013-12-11 19:41:16 +01002082 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002083 b_reset(&check->bi);
2084 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02002085
Olivier Houchardaebeff72019-11-29 16:18:51 +01002086 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09002087 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002088 cs = check->cs;
2089 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02002090
Willy Tarreaufb56aab2012-09-28 14:40:02 +02002091 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02002092 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002093 goto out_unlock;
2094
Willy Tarreaue7dff022015-04-03 01:14:29 +02002095 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02002096 /* we allow up to min(inter, timeout.connect) for a connection
2097 * to establish but only when timeout.check is set
2098 * as it may be to short for a full check otherwise
2099 */
Simon Horman4a741432013-02-23 15:35:38 +09002100 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002101 if (proxy->timeout.check && proxy->timeout.connect) {
2102 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02002103 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02002104 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01002105
Willy Tarreaucc705a62019-09-05 17:51:30 +02002106 if (check->type) {
2107 /* send the request if we have one. We avoid receiving
2108 * if not connected, unless we didn't subscribe for
2109 * sending since otherwise we won't be woken up.
2110 */
2111 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01002112 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02002113 !(check->wait_list.events & SUB_RETRY_SEND))
2114 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02002115 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01002116
Willy Tarreau5a78f362012-11-23 12:47:05 +01002117 goto reschedule;
2118
Willy Tarreaue7dff022015-04-03 01:14:29 +02002119 case SF_ERR_SRVTO: /* ETIMEDOUT */
2120 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02002121 if (conn)
2122 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02002123 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01002124 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02002125 /* should share same code than cases below */
2126 case SF_ERR_CHK_PORT:
2127 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02002128 case SF_ERR_PRXCOND:
2129 case SF_ERR_RESOURCE:
2130 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02002131 if (conn)
2132 conn->flags |= CO_FL_ERROR;
2133 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01002134 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002135 }
2136
Willy Tarreau5a78f362012-11-23 12:47:05 +01002137 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01002138 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002139 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02002140 if (check->wait_list.events)
2141 cs->conn->xprt->unsubscribe(cs->conn,
2142 cs->conn->xprt_ctx,
2143 check->wait_list.events,
2144 &check->wait_list);
2145 /* We may have been scheduled to run, and the
2146 * I/O handler expects to have a cs, so remove
2147 * the tasklet
2148 */
Willy Tarreau86eded62019-06-14 14:47:49 +02002149 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002150 cs_destroy(cs);
2151 cs = check->cs = NULL;
2152 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02002153 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01002154
Willy Tarreau2c115e52013-12-11 19:41:16 +01002155 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02002156 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02002157
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01002158 /* we allow up to min(inter, timeout.connect) for a connection
2159 * to establish but only when timeout.check is set
2160 * as it may be to short for a full check otherwise
2161 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02002162 while (tick_is_expired(t->expire, now_ms)) {
2163 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01002164
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002165 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09002166 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002167 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02002168 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01002169 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002170 }
2171 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02002172 /* there was a test running.
2173 * First, let's check whether there was an uncaught error,
2174 * which can happen on connect timeout or error.
2175 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09002176 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01002177 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01002178 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01002179 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09002180 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02002181 else
Simon Horman4a741432013-02-23 15:35:38 +09002182 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01002183 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02002184 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02002185 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02002186 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01002187 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002188 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02002189 }
2190
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01002191 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002192
2193 check->current_step = NULL;
2194 if (check->sess != NULL) {
2195 session_free(check->sess);
2196 check->sess = NULL;
2197 }
2198
Willy Tarreau00149122017-10-04 18:05:01 +02002199 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01002200 /* The check was aborted and the connection was not yet closed.
2201 * This can happen upon timeout, or when an external event such
2202 * as a failed response coupled with "observe layer7" caused the
2203 * server state to be suddenly changed.
2204 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01002205 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02002206 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01002207 }
2208
Willy Tarreauac59f362017-10-08 11:10:19 +02002209 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02002210 if (check->wait_list.events)
2211 cs->conn->xprt->unsubscribe(cs->conn,
2212 cs->conn->xprt_ctx,
2213 check->wait_list.events,
2214 &check->wait_list);
2215 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02002216 * I/O handler expects to have a cs, so remove
2217 * the tasklet
2218 */
2219 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002220 cs_destroy(cs);
2221 cs = check->cs = NULL;
2222 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02002223 }
2224
Olivier Houchard0923fa42019-01-11 18:43:04 +01002225 if (check->server) {
2226 if (check->result == CHK_RES_FAILED) {
2227 /* a failure or timeout detected */
2228 check_notify_failure(check);
2229 }
2230 else if (check->result == CHK_RES_CONDPASS) {
2231 /* check is OK but asks for stopping mode */
2232 check_notify_stopping(check);
2233 }
2234 else if (check->result == CHK_RES_PASSED) {
2235 /* a success was detected */
2236 check_notify_success(check);
2237 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002238 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02002239 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002240 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002241
Olivier Houchard0923fa42019-01-11 18:43:04 +01002242 if (check->server) {
2243 rv = 0;
2244 if (global.spread_checks > 0) {
2245 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002246 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01002247 }
2248 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02002249 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002250 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01002251
2252 reschedule:
2253 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09002254 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002255 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01002256 if (check->server)
2257 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01002258 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002259}
2260
Simon Horman98637e52014-06-20 12:30:16 +09002261/*
2262 * manages a server health-check. Returns
2263 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
2264 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002265static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09002266{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002267 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09002268
2269 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02002270 return process_chk_proc(t, context, state);
2271 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02002272
Simon Horman98637e52014-06-20 12:30:16 +09002273}
2274
Simon Horman5c942422013-11-25 10:46:32 +09002275static int start_check_task(struct check *check, int mininter,
2276 int nbcheck, int srvpos)
2277{
2278 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002279 unsigned long thread_mask = MAX_THREADS_MASK;
2280
2281 if (check->type == PR_O2_EXT_CHK)
2282 thread_mask = 1;
2283
Simon Horman5c942422013-11-25 10:46:32 +09002284 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002285 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002286 ha_alert("Starting [%s:%s] check: out of memory.\n",
2287 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002288 return 0;
2289 }
2290
2291 check->task = t;
2292 t->process = process_chk;
2293 t->context = check;
2294
Willy Tarreau1746eec2014-04-25 10:46:47 +02002295 if (mininter < srv_getinter(check))
2296 mininter = srv_getinter(check);
2297
2298 if (global.max_spread_checks && mininter > global.max_spread_checks)
2299 mininter = global.max_spread_checks;
2300
Simon Horman5c942422013-11-25 10:46:32 +09002301 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002302 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002303 check->start = now;
2304 task_queue(t);
2305
2306 return 1;
2307}
2308
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002309/*
2310 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002311 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002312 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002313static int start_checks()
2314{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002315
2316 struct proxy *px;
2317 struct server *s;
2318 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002319 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002320
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002321 /* 0- init the dummy frontend used to create all checks sessions */
2322 init_new_proxy(&checks_fe);
2323 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2324 checks_fe.mode = PR_MODE_TCP;
2325 checks_fe.maxconn = 0;
2326 checks_fe.conn_retries = CONN_RETRIES;
2327 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2328 checks_fe.timeout.client = TICK_ETERNITY;
2329
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002330 /* 1- count the checkers to run simultaneously.
2331 * We also determine the minimum interval among all of those which
2332 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2333 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002334 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002335 * too short an interval for all others.
2336 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002337 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002338 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002339 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002340 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002341 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002342 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002343 }
2344 /* We need a warmup task that will be called when the server
2345 * state switches from down to up.
2346 */
2347 s->warmup = t;
2348 t->process = server_warmup;
2349 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002350 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002351 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002352 task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, (now.tv_sec - s->last_change)) / 20)));
Willy Tarreaue7b73482013-11-21 11:50:50 +01002353 }
2354
Willy Tarreaud8514a22013-12-11 21:10:14 +01002355 if (s->check.state & CHK_ST_CONFIGURED) {
2356 nbcheck++;
2357 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2358 (!mininter || mininter > srv_getinter(&s->check)))
2359 mininter = srv_getinter(&s->check);
2360 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002361
Willy Tarreaud8514a22013-12-11 21:10:14 +01002362 if (s->agent.state & CHK_ST_CONFIGURED) {
2363 nbcheck++;
2364 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2365 (!mininter || mininter > srv_getinter(&s->agent)))
2366 mininter = srv_getinter(&s->agent);
2367 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002368 }
2369 }
2370
Simon Horman4a741432013-02-23 15:35:38 +09002371 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002372 return 0;
2373
2374 srand((unsigned)time(NULL));
2375
2376 /*
2377 * 2- start them as far as possible from each others. For this, we will
2378 * start them after their interval set to the min interval divided by
2379 * the number of servers, weighted by the server's position in the list.
2380 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002381 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002382 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2383 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002384 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002385 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002386 }
2387 }
2388
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002389 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002390 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002391 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002392 if (s->check.type == PR_O2_EXT_CHK) {
2393 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002394 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002395 }
Simon Hormand60d6912013-11-25 10:46:36 +09002396 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002397 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002398 srvpos++;
2399 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002400
Simon Hormand60d6912013-11-25 10:46:36 +09002401 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002402 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002403 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002404 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002405 }
2406 srvpos++;
2407 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002408 }
2409 }
2410 return 0;
2411}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002412
2413/*
Willy Tarreau5b3a2022012-09-28 15:01:02 +02002414 * Perform content verification check on data in s->check.buffer buffer.
Willy Tarreaubd741542010-03-16 18:46:54 +01002415 * The buffer MUST be terminated by a null byte before calling this function.
2416 * Sets server status appropriately. The caller is responsible for ensuring
2417 * that the buffer contains at least 13 characters. If <done> is zero, we may
2418 * return 0 to indicate that data is required to decide of a match.
2419 */
2420static int httpchk_expect(struct server *s, int done)
2421{
Christopher Faulet1bc04c72017-10-29 20:14:08 +01002422 static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>";
Willy Tarreaubd741542010-03-16 18:46:54 +01002423 char status_code[] = "000";
2424 char *contentptr;
2425 int crlf;
2426 int ret;
2427
2428 switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
2429 case PR_O2_EXP_STS:
2430 case PR_O2_EXP_RSTS:
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002431 memcpy(status_code, b_head(&s->check.bi) + 9, 3);
2432 memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3);
Willy Tarreaubd741542010-03-16 18:46:54 +01002433
2434 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
2435 ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
2436 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002437 ret = regex_exec(s->proxy->expect_regex, status_code);
Willy Tarreaubd741542010-03-16 18:46:54 +01002438
2439 /* we necessarily have the response, so there are no partial failures */
2440 if (s->proxy->options2 & PR_O2_EXP_INV)
2441 ret = !ret;
2442
Simon Horman4a741432013-02-23 15:35:38 +09002443 set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
Willy Tarreaubd741542010-03-16 18:46:54 +01002444 break;
2445
2446 case PR_O2_EXP_STR:
2447 case PR_O2_EXP_RSTR:
2448 /* very simple response parser: ignore CR and only count consecutive LFs,
2449 * stop with contentptr pointing to first char after the double CRLF or
2450 * to '\0' if crlf < 2.
2451 */
2452 crlf = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002453 for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) {
Willy Tarreaubd741542010-03-16 18:46:54 +01002454 if (crlf >= 2)
2455 break;
2456 if (*contentptr == '\r')
2457 continue;
2458 else if (*contentptr == '\n')
2459 crlf++;
2460 else
2461 crlf = 0;
2462 }
2463
2464 /* Check that response contains a body... */
2465 if (crlf < 2) {
2466 if (!done)
2467 return 0;
2468
Simon Horman4a741432013-02-23 15:35:38 +09002469 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002470 "HTTP content check could not find a response body");
2471 return 1;
2472 }
2473
2474 /* Check that response body is not empty... */
2475 if (*contentptr == '\0') {
Willy Tarreaua164fb52011-04-13 09:32:41 +02002476 if (!done)
2477 return 0;
2478
Simon Horman4a741432013-02-23 15:35:38 +09002479 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002480 "HTTP content check found empty response body");
2481 return 1;
2482 }
2483
2484 /* Check the response content against the supplied string
2485 * or regex... */
2486 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
2487 ret = strstr(contentptr, s->proxy->expect_str) != NULL;
2488 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002489 ret = regex_exec(s->proxy->expect_regex, contentptr);
Willy Tarreaubd741542010-03-16 18:46:54 +01002490
2491 /* if we don't match, we may need to wait more */
2492 if (!ret && !done)
2493 return 0;
2494
2495 if (ret) {
2496 /* content matched */
2497 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002498 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002499 "HTTP check matched unwanted content");
2500 else
Simon Horman4a741432013-02-23 15:35:38 +09002501 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002502 "HTTP content check matched");
2503 }
2504 else {
2505 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002506 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002507 "HTTP check did not match unwanted content");
2508 else
Simon Horman4a741432013-02-23 15:35:38 +09002509 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002510 "HTTP content check did not match");
2511 }
2512 break;
2513 }
2514 return 1;
2515}
2516
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002517/*
2518 * return the id of a step in a send/expect session
2519 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002520static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002521{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002522 if (!rule)
2523 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002524
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002525 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002526 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002527 return 1;
2528
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002529 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002530 if (rule->index == 0 &&
2531 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002532 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002533 return 0;
2534
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002535 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002536}
2537
Christopher Faulet206368d2020-04-03 14:51:06 +02002538static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2539 int match, struct ist info)
2540{
2541 struct sample *smp;
2542
2543 if (istlen(info)) {
2544 chunk_strncat(msg, info.ptr, info.len);
2545 goto comment;
2546 }
2547 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2548 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2549 goto comment;
2550 }
2551
2552 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2553 switch (rule->expect.type) {
2554 case TCPCHK_EXPECT_STRING:
2555 chunk_appendf(msg, " '%s' at step %d", rule->expect.string, tcpcheck_get_step_id(check, rule));
2556 break;
2557 case TCPCHK_EXPECT_BINARY:
2558 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2559 break;
2560 case TCPCHK_EXPECT_REGEX:
2561 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2562 break;
2563 case TCPCHK_EXPECT_REGEX_BINARY:
2564 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2565
2566 /* If references to the matched text were made, divide the
2567 * offsets by 2 to match offset of the original response buffer.
2568 */
2569 if (rule->expect.with_capture) {
2570 int i;
2571
2572 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2573 pmatch[i].rm_so /= 2; /* at first matched char. */
2574 pmatch[i].rm_eo /= 2; /* at last matched char. */
2575 }
2576 }
2577 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002578 case TCPCHK_EXPECT_CUSTOM:
2579 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2580 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002581 case TCPCHK_EXPECT_UNDEF:
2582 /* Should never happen. */
2583 return;
2584 }
2585
2586 comment:
2587 if (rule->comment) {
2588 chunk_strcat(msg, " comment: ");
2589 if (rule->expect.with_capture) {
2590 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2591 if (ret != -1) /* ignore comment if too large */
2592 msg->data += ret;
2593 }
2594 else
2595 chunk_strcat(msg, rule->comment);
2596 }
2597
2598 if (rule->expect.status_expr) {
2599 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2600 rule->expect.status_expr, SMP_T_SINT);
2601 if (smp)
2602 check->code = smp->data.u.sint;
2603 }
2604
2605 *(b_tail(msg)) = '\0';
2606}
2607
2608static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2609 struct ist info)
2610{
2611 struct sample *smp;
2612
2613 if (istlen(info))
2614 chunk_strncat(msg, info.ptr, info.len);
2615 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2616 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2617 &rule->expect.onsuccess_fmt);
2618 else
2619 chunk_strcat(msg, "(tcp-check)");
2620
2621 if (rule->expect.status_expr) {
2622 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2623 rule->expect.status_expr, SMP_T_SINT);
2624 if (smp)
2625 check->code = smp->data.u.sint;
2626 }
2627
2628 *(b_tail(msg)) = '\0';
2629}
2630
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002631static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2632 unsigned int offset, int last_read)
2633{
2634 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2635 enum healthcheck_status status;
2636 struct buffer *msg = NULL;
2637 struct ist desc = ist(NULL);
2638 unsigned int err = 0, plen = 0;
2639
2640
2641 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2642 if (!last_read && b_data(&check->bi) < offset+4) {
2643 if (!last_read)
2644 goto wait_more_data;
2645
2646 /* invalid length or truncated response */
2647 status = HCHK_STATUS_L7RSP;
2648 goto error;
2649 }
2650
2651 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2652 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2653 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2654
2655 if (b_data(&check->bi) < offset+plen+4) {
2656 if (!last_read)
2657 goto wait_more_data;
2658
2659 /* invalid length or truncated response */
2660 status = HCHK_STATUS_L7RSP;
2661 goto error;
2662 }
2663
2664 if (*b_peek(&check->bi, offset+4) == '\xff') {
2665 /* MySQL Error packet always begin with field_count = 0xff */
2666 status = HCHK_STATUS_L7STS;
2667 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2668 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2669 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2670 goto error;
2671 }
2672
2673 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2674 /* Not the last rule, continue */
2675 goto out;
2676 }
2677
2678 /* We set the MySQL Version in description for information purpose
2679 * FIXME : it can be cool to use MySQL Version for other purpose,
2680 * like mark as down old MySQL server.
2681 */
2682 set_server_check_status(check, HCHK_STATUS_L7OKD, b_peek(&check->bi, 5));
2683
2684 out:
2685 free_trash_chunk(msg);
2686 return ret;
2687
2688 error:
2689 ret = TCPCHK_EVAL_STOP;
2690 check->code = err;
2691 msg = alloc_trash_chunk();
2692 if (msg)
2693 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2694 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2695 goto out;
2696
2697 wait_more_data:
2698 ret = TCPCHK_EVAL_WAIT;
2699 goto out;
2700}
2701
2702
2703static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2704{
2705 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2706}
2707
2708static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2709{
2710 unsigned int hslen = 0;
2711
2712 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2713 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2714 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2715
2716 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2717}
2718
Christopher Faulet1997eca2020-04-03 23:13:50 +02002719static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2720{
2721 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2722 enum healthcheck_status status;
2723 struct buffer *msg = NULL;
2724 struct ist desc = ist(NULL);
2725 unsigned short msglen = 0;
2726
2727 /* Check if the server speaks LDAP (ASN.1/BER)
2728 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2729 * http://tools.ietf.org/html/rfc4511
2730 */
2731 /* size of LDAPMessage */
2732 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2733
2734 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2735 * messageID: 0x02 0x01 0x01: INTEGER 1
2736 * protocolOp: 0x61: bindResponse
2737 */
2738 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2739 status = HCHK_STATUS_L7RSP;
2740 desc = ist("Not LDAPv3 protocol");
2741 goto error;
2742 }
2743
2744 /* size of bindResponse */
2745 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2746
2747 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2748 * ldapResult: 0x0a 0x01: ENUMERATION
2749 */
2750 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2751 status = HCHK_STATUS_L7RSP;
2752 desc = ist("Not LDAPv3 protocol");
2753 goto error;
2754 }
2755
2756 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2757 * resultCode
2758 */
2759 check->code = *(b_head(&check->bi) + msglen + 9);
2760 if (check->code) {
2761 status = HCHK_STATUS_L7STS;
2762 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2763 goto error;
2764 }
2765
2766 set_server_check_status(check, HCHK_STATUS_L7OKD, "Success");
2767
2768 out:
2769 free_trash_chunk(msg);
2770 return ret;
2771
2772 error:
2773 ret = TCPCHK_EVAL_STOP;
2774 msg = alloc_trash_chunk();
2775 if (msg)
2776 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2777 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2778 goto out;
2779
2780 wait_more_data:
2781 ret = TCPCHK_EVAL_WAIT;
2782 goto out;
2783}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002784
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002785/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2786 * to wait and -1 to stop the check. */
2787static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002788{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002789 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2790 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002791 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002792 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002793 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002794 struct conn_stream *cs;
2795 struct connection *conn = NULL;
2796 struct protocol *proto;
2797 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002798 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002799
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002800 /* For a connect action we'll create a new connection. We may also have
2801 * to kill a previous one. But we don't want to leave *without* a
2802 * connection if we came here from the connection layer, hence with a
2803 * connection. Thus we'll proceed in the following order :
2804 * 1: close but not release previous connection (handled by the caller)
2805 * 2: try to get a new connection
2806 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002807 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002808
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002809 /* 2- prepare new connection */
2810 cs = cs_new(NULL);
2811 if (!cs) {
2812 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2813 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002814 if (rule->comment)
2815 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002816 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2817 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002818 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002819 }
2820
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002821 /* 3- release and replace the old one on success */
2822 if (check->cs) {
2823 if (check->wait_list.events)
2824 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2825 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002826
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002827 /* We may have been scheduled to run, and the I/O handler
2828 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002829 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002830 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2831 cs_destroy(check->cs);
2832 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002833
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002834 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002835
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002836 check->cs = cs;
2837 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002838
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002839 /* Maybe there were an older connection we were waiting on */
2840 check->wait_list.events = 0;
2841 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002842
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002843 /* no client address */
2844 if (!sockaddr_alloc(&conn->dst)) {
2845 status = SF_ERR_RESOURCE;
2846 goto fail_check;
2847 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002848
Christopher Faulet5c288742020-03-31 08:15:58 +02002849 /* connect to the connect rule addr if specified, otherwise the check
2850 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002851 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002852 *conn->dst = (is_addr(&connect->addr)
2853 ? connect->addr
2854 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002855 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002856
Christopher Faulet5c288742020-03-31 08:15:58 +02002857 port = 0;
2858 if (!port && connect->port)
2859 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002860 if (!port && connect->port_expr) {
2861 struct sample *smp;
2862
2863 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2864 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2865 connect->port_expr, SMP_T_SINT);
2866 if (smp)
2867 port = smp->data.u.sint;
2868 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002869 if (!port && is_inet_addr(&connect->addr))
2870 port = get_host_port(&connect->addr);
2871 if (!port && check->port)
2872 port = check->port;
2873 if (!port && is_inet_addr(&check->addr))
2874 port = get_host_port(&check->addr);
2875 if (!port)
2876 port = s->svc_port;
2877 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002878
Christopher Fauletbb591a12020-04-01 16:52:17 +02002879 xprt = ((connect->options & TCPCHK_OPT_SSL)
2880 ? xprt_get(XPRT_SSL)
2881 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002882
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002883 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002884 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002885 status = SF_ERR_RESOURCE;
2886 goto fail_check;
2887 }
2888 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002889
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002890 status = SF_ERR_INTERNAL;
2891 if (proto && proto->connect) {
2892 struct tcpcheck_rule *next;
2893 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002894
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002895 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2896 if (!next || next->action != TCPCHK_ACT_EXPECT)
2897 flags |= CONNECT_DELACK_ALWAYS;
2898 status = proto->connect(conn, flags);
2899 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002900
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002901#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002902 if (status == SF_ERR_NONE) {
2903 if (connect->sni)
2904 ssl_sock_set_servername(conn, connect->sni);
2905 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2906 ssl_sock_set_servername(conn, s->check.sni);
2907
2908 if (connect->alpn)
2909 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2910 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2911 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002912 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002913#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002914 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2915 conn->send_proxy_ofs = 1;
2916 conn->flags |= CO_FL_SOCKS4;
2917 }
2918 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2919 conn->send_proxy_ofs = 1;
2920 conn->flags |= CO_FL_SOCKS4;
2921 }
2922
2923 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2924 conn->send_proxy_ofs = 1;
2925 conn->flags |= CO_FL_SEND_PROXY;
2926 }
2927 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2928 conn->send_proxy_ofs = 1;
2929 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002930 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002931
Christopher Fauletbb591a12020-04-01 16:52:17 +02002932 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2933 /* Some servers don't like reset on close */
2934 fdtab[cs->conn->handle.fd].linger_risk = 0;
2935 }
2936
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002937 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2938 if (xprt_add_hs(conn) < 0)
2939 status = SF_ERR_RESOURCE;
2940 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002941
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002942 fail_check:
2943 /* It can return one of :
2944 * - SF_ERR_NONE if everything's OK
2945 * - SF_ERR_SRVTO if there are no more servers
2946 * - SF_ERR_SRVCL if the connection was refused by the server
2947 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2948 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2949 * - SF_ERR_INTERNAL for any other purely internal errors
2950 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2951 * Note that we try to prevent the network stack from sending the ACK during the
2952 * connect() when a pure TCP check is used (without PROXY protocol).
2953 */
2954 switch (status) {
2955 case SF_ERR_NONE:
2956 /* we allow up to min(inter, timeout.connect) for a connection
2957 * to establish but only when timeout.check is set as it may be
2958 * to short for a full check otherwise
2959 */
2960 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002961
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002962 if (proxy->timeout.check && proxy->timeout.connect) {
2963 int t_con = tick_add(now_ms, proxy->timeout.connect);
2964 t->expire = tick_first(t->expire, t_con);
2965 }
2966 break;
2967 case SF_ERR_SRVTO: /* ETIMEDOUT */
2968 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
2969 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
2970 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002971 if (rule->comment)
2972 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002973 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
2974 ret = TCPCHK_EVAL_STOP;
2975 goto out;
2976 case SF_ERR_PRXCOND:
2977 case SF_ERR_RESOURCE:
2978 case SF_ERR_INTERNAL:
2979 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
2980 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002981 if (rule->comment)
2982 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002983 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2984 ret = TCPCHK_EVAL_STOP;
2985 goto out;
2986 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002987
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002988 /* don't do anything until the connection is established */
2989 if (conn->flags & CO_FL_WAIT_XPRT) {
2990 ret = TCPCHK_EVAL_WAIT;
2991 goto out;
2992 }
Willy Tarreaube373152018-09-06 11:45:30 +02002993
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002994 out:
2995 if (conn && check->result == CHK_RES_FAILED)
2996 conn->flags |= CO_FL_ERROR;
2997 return ret;
2998}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002999
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003000/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
3001 * to wait and -1 to stop the check. */
3002static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
3003{
3004 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3005 struct tcpcheck_send *send = &rule->send;
3006 struct conn_stream *cs = check->cs;
3007 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003008 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003009
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003010 /* reset the read & write buffer */
3011 b_reset(&check->bi);
3012 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01003013
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003014 switch (send->type) {
3015 case TCPCHK_SEND_STRING:
3016 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003017 if (istlen(send->data) >= b_size(&check->bo)) {
3018 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
3019 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
3020 tcpcheck_get_step_id(check, rule));
3021 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3022 ret = TCPCHK_EVAL_STOP;
3023 goto out;
3024 }
3025 b_putist(&check->bo, send->data);
3026 break;
3027 case TCPCHK_SEND_STRING_LF:
3028 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
3029 if (!b_data(&check->bo))
3030 goto out;
3031 break;
3032 case TCPCHK_SEND_BINARY_LF:
3033 tmp = alloc_trash_chunk();
3034 if (!tmp)
3035 goto error_lf;
3036 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
3037 if (!b_data(tmp))
3038 goto out;
3039 tmp->area[tmp->data] = '\0';
3040 b_set_data(&check->bo, b_size(&check->bo));
3041 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
3042 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003043 break;
3044 case TCPCHK_SEND_UNDEF:
3045 /* Should never happen. */
3046 ret = TCPCHK_EVAL_STOP;
3047 goto out;
3048 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003049
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003050 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
3051 ret = TCPCHK_EVAL_WAIT;
3052 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
3053 ret = TCPCHK_EVAL_STOP;
3054 goto out;
3055 }
3056 if (b_data(&check->bo)) {
3057 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3058 ret = TCPCHK_EVAL_WAIT;
3059 goto out;
3060 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003061
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003062 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003063 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003064 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003065
3066 error_lf:
3067 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
3068 tcpcheck_get_step_id(check, rule));
3069 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3070 ret = TCPCHK_EVAL_STOP;
3071 goto out;
3072
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003073}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003074
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003075/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003076 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003077 */
3078static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3079{
3080 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3081 struct tcpcheck_expect *expect = &check->current_step->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003082 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003083 int match;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003084
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003085 /* The current expect might need more data than the previous one, check again
3086 * that the minimum amount data required to match is respected.
3087 */
3088 if (!last_read) {
3089 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
3090 (b_data(&check->bi) < expect->length)) {
3091 ret = TCPCHK_EVAL_WAIT;
3092 goto out;
3093 }
3094 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3095 ret = TCPCHK_EVAL_WAIT;
3096 goto out;
3097 }
3098 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003099
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003100 /* Make GCC happy ; initialize match to a failure state. */
3101 match = expect->inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003102
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003103 switch (expect->type) {
3104 case TCPCHK_EXPECT_STRING:
3105 case TCPCHK_EXPECT_BINARY:
3106 match = my_memmem(b_head(&check->bi), b_data(&check->bi), expect->string, expect->length) != NULL;
3107 break;
3108 case TCPCHK_EXPECT_REGEX:
3109 if (expect->with_capture)
3110 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3111 MAX_MATCH, pmatch, 0);
3112 else
3113 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3114 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003115
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003116 case TCPCHK_EXPECT_REGEX_BINARY:
3117 chunk_reset(&trash);
3118 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
3119 if (expect->with_capture)
3120 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3121 MAX_MATCH, pmatch, 0);
3122 else
3123 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3124 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003125 case TCPCHK_EXPECT_CUSTOM:
3126 if (expect->custom)
3127 ret = expect->custom(check, rule, last_read);
3128 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003129 case TCPCHK_EXPECT_UNDEF:
3130 /* Should never happen. */
3131 ret = TCPCHK_EVAL_STOP;
3132 goto out;
3133 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003134
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003135
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003136 /* Wait for more data on mismatch only if no minimum is defined (-1),
3137 * otherwise the absence of match is already conclusive.
3138 */
3139 if (!match && !last_read && (expect->min_recv == -1)) {
3140 ret = TCPCHK_EVAL_WAIT;
3141 goto out;
3142 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003143
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003144 /* Result as expected, next rule. */
3145 if (match ^ expect->inverse)
3146 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003147
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003148
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003149 /* From this point on, we matched something we did not want, this is an error state. */
3150 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003151 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003152 if (msg)
3153 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003154 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003155 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003156 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003157
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003158 out:
3159 return ret;
3160}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003161
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003162/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3163 * to wait and -1 to stop the check.
3164 */
3165static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3166{
3167 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3168 struct act_rule *act_rule;
3169 enum act_return act_ret;
3170
3171 act_rule =rule->action_kw.rule;
3172 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3173 if (act_ret != ACT_RET_CONT) {
3174 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3175 tcpcheck_get_step_id(check, rule));
3176 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3177 ret = TCPCHK_EVAL_STOP;
3178 }
3179
3180 return ret;
3181}
3182
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003183/* proceed with next steps for the TCP checks <check>. Note that this is called
3184 * both from the connection's wake() callback and from the check scheduling task.
3185 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3186 * connection, presenting the risk of an fd replacement.
3187 *
3188 * Please do NOT place any return statement in this function and only leave
3189 * via the out_end_tcpcheck label after setting retcode.
3190 */
3191static int tcpcheck_main(struct check *check)
3192{
3193 struct tcpcheck_rule *rule;
3194 struct conn_stream *cs = check->cs;
3195 struct connection *conn = cs_conn(cs);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003196 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003197 int must_read = 1, last_read = 0;
3198 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003199
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003200 /* here, we know that the check is complete or that it failed */
3201 if (check->result != CHK_RES_UNKNOWN)
3202 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003203
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003204 /* 1- check for connection error, if any */
3205 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3206 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003207
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003208 /* 2- check if we are waiting for the connection establishment. It only
3209 * happens during TCPCHK_ACT_CONNECT. */
3210 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3211 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003212
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003213 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3214 if (conn && b_data(&check->bo)) {
3215 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3216 if (ret <= 0) {
3217 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3218 goto out_end_tcpcheck;
3219 goto out;
3220 }
3221 if (b_data(&check->bo)) {
3222 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3223 goto out;
3224 }
3225 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003226
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003227 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003228
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003229 /* If check->current_step is defined, we are in resume condition. For
3230 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3231 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3232 * re-evaluate the current rule. Others cannot yield.
3233 */
3234 if (check->current_step) {
3235 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3236 check->current_step->action == TCPCHK_ACT_SEND)
3237 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3238 else
3239 rule = check->current_step;
3240 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003241 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003242 struct tcpcheck_var *var;
3243
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003244 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003245 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003246 if (!check->sess) {
3247 chunk_printf(&trash, "TCPCHK error allocating check session");
3248 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3249 goto out_end_tcpcheck;
3250 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003251 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003252 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003253
3254 /* Preset tcp-check variables */
3255 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3256 struct sample smp;
3257
3258 memset(&smp, 0, sizeof(smp));
3259 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3260 smp.data = var->data;
3261 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3262 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003263 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003264
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003265 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003266 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003267
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003268 switch (rule->action) {
3269 case TCPCHK_ACT_CONNECT:
3270 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003271
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003272 /* close but not release yet previous connection */
3273 if (check->cs) {
3274 cs_close(check->cs);
3275 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003276 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003277 eval_ret = tcpcheck_eval_connect(check, rule);
3278 must_read = 1; last_read = 0;
3279 break;
3280 case TCPCHK_ACT_SEND:
3281 check->current_step = rule;
3282 eval_ret = tcpcheck_eval_send(check, rule);
3283 must_read = 1;
3284 break;
3285 case TCPCHK_ACT_EXPECT:
3286 check->current_step = rule;
3287 if (must_read) {
3288 if (check->proxy->timeout.check)
3289 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003290
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003291 /* If we already subscribed, then we tried to received and
3292 * failed, so there's no point trying again.
3293 */
3294 if (check->wait_list.events & SUB_RETRY_RECV)
3295 goto out;
3296 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3297 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3298 last_read = 1;
3299 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3300 /* Report network errors only if we got no other data. Otherwise
3301 * we'll let the upper layers decide whether the response is OK
3302 * or not. It is very common that an RST sent by the server is
3303 * reported as an error just after the last data chunk.
3304 */
3305 goto out_end_tcpcheck;
3306 }
3307 }
3308 else {
3309 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3310 goto out;
3311 }
3312 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003313
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003314 /* buffer full, don't wait for more data */
3315 if (b_full(&check->bi))
3316 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003317
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003318 /* Check that response body is not empty... */
3319 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003320 if (!last_read)
3321 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003322
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003323 /* empty response */
3324 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3325 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003326 if (rule->comment)
3327 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003328 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003329 ret = -1;
3330 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003331 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003332 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003333 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003334
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003335 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3336 if (eval_ret == TCPCHK_EVAL_WAIT) {
3337 check->current_step = rule->expect.head;
3338 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003339 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003340 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003341 case TCPCHK_ACT_ACTION_KW:
3342 /* Don't update the current step */
3343 eval_ret = tcpcheck_eval_action_kw(check, rule);
3344 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003345 default:
3346 /* Otherwise, just go to the next one and don't update
3347 * the current step
3348 */
3349 eval_ret = TCPCHK_EVAL_CONTINUE;
3350 break;
3351 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003352
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003353 switch (eval_ret) {
3354 case TCPCHK_EVAL_CONTINUE:
3355 break;
3356 case TCPCHK_EVAL_WAIT:
3357 goto out;
3358 case TCPCHK_EVAL_STOP:
3359 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003360 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003361 }
3362
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003363 /* All rules was evaluated */
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003364 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet206368d2020-04-03 14:51:06 +02003365 msg = alloc_trash_chunk();
3366 if (msg)
3367 tcpcheck_onsuccess_message(msg, check, check->current_step, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003368 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003369 set_server_check_status(check, HCHK_STATUS_L7OKD, (msg ? b_head(msg) : "(tcp-check)"));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003370 free_trash_chunk(msg);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003371
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003372 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003373 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003374 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003375
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003376 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003377 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003378 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003379 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003380 session_free(check->sess);
3381 check->sess = NULL;
3382 }
3383 out:
3384 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003385}
3386
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003387static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003388{
3389 check->type = type;
3390
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003391 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3392 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003393
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003394 check->bi.area = calloc(check->bi.size, sizeof(char));
3395 check->bo.area = calloc(check->bo.size, sizeof(char));
3396
3397 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003398 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003399
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003400 check->wait_list.tasklet = tasklet_new();
3401 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003402 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003403 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003404 check->wait_list.tasklet->process = event_srv_chk_io;
3405 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003406 return NULL;
3407}
3408
Simon Hormanbfb5d332015-01-30 11:22:55 +09003409void free_check(struct check *check)
3410{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003411 task_destroy(check->task);
3412 if (check->wait_list.tasklet)
3413 tasklet_free(check->wait_list.tasklet);
3414
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003415 free(check->bi.area);
3416 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003417 if (check->cs) {
3418 free(check->cs->conn);
3419 check->cs->conn = NULL;
3420 cs_free(check->cs);
3421 check->cs = NULL;
3422 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003423}
3424
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003425static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3426{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003427 struct logformat_node *lf, *lfb;
3428
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003429 if (!rule)
3430 return;
3431
3432 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003433 switch (rule->action) {
3434 case TCPCHK_ACT_SEND:
3435 switch (rule->send.type) {
3436 case TCPCHK_SEND_STRING:
3437 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003438 free(rule->send.data.ptr);
3439 break;
3440 case TCPCHK_SEND_STRING_LF:
3441 case TCPCHK_SEND_BINARY_LF:
3442 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3443 LIST_DEL(&lf->list);
3444 release_sample_expr(lf->expr);
3445 free(lf->arg);
3446 free(lf);
3447 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003448 break;
3449 case TCPCHK_SEND_UNDEF:
3450 break;
3451 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003452 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003453 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003454 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3455 LIST_DEL(&lf->list);
3456 release_sample_expr(lf->expr);
3457 free(lf->arg);
3458 free(lf);
3459 }
3460 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3461 LIST_DEL(&lf->list);
3462 release_sample_expr(lf->expr);
3463 free(lf->arg);
3464 free(lf);
3465 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003466 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003467 switch (rule->expect.type) {
3468 case TCPCHK_EXPECT_STRING:
3469 case TCPCHK_EXPECT_BINARY:
3470 free(rule->expect.string);
3471 break;
3472 case TCPCHK_EXPECT_REGEX:
3473 case TCPCHK_EXPECT_REGEX_BINARY:
3474 regex_free(rule->expect.regex);
3475 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003476 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003477 case TCPCHK_EXPECT_UNDEF:
3478 break;
3479 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003480 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003481 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003482 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003483 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003484 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003485 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003486 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003487 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003488 case TCPCHK_ACT_ACTION_KW:
3489 free(rule->action_kw.rule);
3490 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003491 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003492
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003493 if (in_pool)
3494 pool_free(pool_head_tcpcheck_rule, rule);
3495 else
3496 free(rule);
3497}
3498
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003499
Christopher Fauletce355072020-04-02 11:44:39 +02003500static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003501{
3502 struct tcpcheck_var *var = NULL;
3503
3504 var = calloc(1, sizeof(*var));
3505 if (var == NULL)
3506 return NULL;
3507
3508 var->name = ist2(strdup(name), strlen(name));
3509 if (var->name.ptr == NULL) {
3510 free(var);
3511 return NULL;
3512 }
3513
3514 LIST_INIT(&var->list);
3515 return var;
3516}
3517
3518static void tcpcheck_var_release(struct tcpcheck_var *var)
3519{
3520 if (!var)
3521 return;
3522
3523 free(var->name.ptr);
3524 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3525 free(var->data.u.str.area);
3526 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3527 free(var->data.u.meth.str.area);
3528 free(var);
3529}
3530
3531int dup_tcpcheck_vars(struct list *dst, struct list *src)
3532{
3533 struct tcpcheck_var *var, *new = NULL;
3534
3535 list_for_each_entry(var, src, list) {
3536 new = tcpcheck_var_create(var->name.ptr);
3537 if (!new)
3538 goto error;
3539 new->data.type = var->data.type;
3540 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3541 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3542 goto error;
3543 if (var->data.type == SMP_T_STR)
3544 new->data.u.str.area[new->data.u.str.data] = 0;
3545 }
3546 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3547 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3548 goto error;
3549 new->data.u.str.area[new->data.u.str.data] = 0;
3550 new->data.u.meth.meth = var->data.u.meth.meth;
3551 }
3552 else
3553 new->data.u = var->data.u;
3554 LIST_ADDQ(dst, &new->list);
3555 }
3556 return 1;
3557
3558 error:
3559 free(new);
3560 return 0;
3561}
3562
3563static void free_tcpcheck_vars(struct list *vars)
3564{
3565 struct tcpcheck_var *var, *back;
3566
3567 list_for_each_entry_safe(var, back, vars, list) {
3568 LIST_DEL(&var->list);
3569 tcpcheck_var_release(var);
3570 }
3571}
3572
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003573void email_alert_free(struct email_alert *alert)
3574{
3575 struct tcpcheck_rule *rule, *back;
3576
3577 if (!alert)
3578 return;
3579
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003580 if (alert->rules.list) {
3581 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3582 LIST_DEL(&rule->list);
3583 free_tcpcheck(rule, 1);
3584 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003585 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003586 free(alert->rules.list);
3587 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003588 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003589 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003590}
3591
Olivier Houchard9f6af332018-05-25 14:04:04 +02003592static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003593{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003594 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003595 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003596 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003597
3598 q = container_of(check, typeof(*q), check);
3599
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003600 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003601 while (1) {
3602 if (!(check->state & CHK_ST_ENABLED)) {
3603 if (LIST_ISEMPTY(&q->email_alerts)) {
3604 /* All alerts processed, queue the task */
3605 t->expire = TICK_ETERNITY;
3606 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003607 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003608 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003609
3610 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003611 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003612 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003613 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003614 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003615 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003616 }
3617
Olivier Houchard9f6af332018-05-25 14:04:04 +02003618 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003619 if (check->state & CHK_ST_INPROGRESS)
3620 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003622 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003623 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003624 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003625 check->server = NULL;
3626 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003627 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003628 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003629 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003630 return t;
3631}
3632
Christopher Faulet0108bb32017-10-20 21:34:32 +02003633/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3634 *
3635 * The function returns 1 in success case, otherwise, it returns 0 and err is
3636 * filled.
3637 */
3638int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003639{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003640 struct mailer *mailer;
3641 struct email_alertq *queues;
3642 const char *err_str;
3643 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003644
Christopher Faulet0108bb32017-10-20 21:34:32 +02003645 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3646 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003647 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003648 }
3649
Christopher Faulet0108bb32017-10-20 21:34:32 +02003650 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3651 struct email_alertq *q = &queues[i];
3652 struct check *check = &q->check;
3653 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003654
3655 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003656 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003657 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003658 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003659 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003660 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003661 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3662 memprintf(err, "%s", err_str);
3663 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003664 }
3665
3666 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003667 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003668 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003669
Emeric Brunc60def82017-09-27 14:59:38 +02003670 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003671 memprintf(err, "out of memory while allocating mailer alerts task");
3672 goto error;
3673 }
3674
3675 check->task = t;
3676 t->process = process_email_alert;
3677 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003678
Christopher Faulet0108bb32017-10-20 21:34:32 +02003679 /* check this in one ms */
3680 t->expire = TICK_ETERNITY;
3681 check->start = now;
3682 task_queue(t);
3683 }
3684
3685 mls->users++;
3686 free(p->email_alert.mailers.name);
3687 p->email_alert.mailers.m = mls;
3688 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003689 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003690
3691 error:
3692 for (i = 0; i < mls->count; i++) {
3693 struct email_alertq *q = &queues[i];
3694 struct check *check = &q->check;
3695
Christopher Faulet0108bb32017-10-20 21:34:32 +02003696 free_check(check);
3697 }
3698 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003699 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003700 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003701}
3702
3703
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003704static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003705{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003706 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003707 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003708
Willy Tarreaubafbe012017-11-24 17:34:44 +01003709 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003710 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003711 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003712 tcpcheck->action = TCPCHK_ACT_EXPECT;
3713
3714 expect = &tcpcheck->expect;
3715 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003716 LIST_INIT(&expect->onerror_fmt);
3717 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003718 expect->err_status = HCHK_STATUS_L7RSP;
3719 expect->tout_status = HCHK_STATUS_L7TOUT;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003720 expect->string = strdup(str);
3721 if (!expect->string) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003722 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003723 return 0;
3724 }
Gaetan Rivetb616add2020-02-07 15:37:17 +01003725 expect->length = strlen(expect->string);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003726
Gaetan Rivet4038b942020-02-26 16:19:40 +01003727 /* All tcp-check expect points back to the first inverse expect rule
3728 * in a chain of one or more expect rule, potentially itself.
3729 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003730 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003731 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003732 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Gaetan Rivetb616add2020-02-07 15:37:17 +01003733 if (prev_check->expect.inverse)
3734 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003735 continue;
3736 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003737 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003738 break;
3739 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003740 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741 return 1;
3742}
3743
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003744static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003745{
3746 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003747 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003748 const char *in;
3749 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003750 int i;
3751
Willy Tarreaubafbe012017-11-24 17:34:44 +01003752 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003753 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003754 memset(tcpcheck, 0, sizeof(*tcpcheck));
3755 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003756
3757 send = &tcpcheck->send;
3758 send->type = TCPCHK_SEND_STRING;
3759
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003760 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003761 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003762
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003763 send->data.ptr = malloc(send->data.len + 1);
3764 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003765 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003766 return 0;
3767 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003768
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003769 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003770 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003771 for (in = strs[i]; (*dst = *in++); dst++);
3772 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003773
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003774 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003775 return 1;
3776}
3777
Christopher Faulet0108bb32017-10-20 21:34:32 +02003778static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3779 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003780{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003781 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003782 struct tcpcheck_rule *tcpcheck;
3783 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003784
Willy Tarreaubafbe012017-11-24 17:34:44 +01003785 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003786 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003787 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003788 alert->rules.flags = 0;
3789 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3790 if (!alert->rules.list)
3791 goto error;
3792 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003793 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003794 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003795
Willy Tarreaubafbe012017-11-24 17:34:44 +01003796 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003797 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003798 memset(tcpcheck, 0, sizeof(*tcpcheck));
3799 tcpcheck->action = TCPCHK_ACT_CONNECT;
3800 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003801
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003802 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003803
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003804 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003805 goto error;
3806
3807 {
3808 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003809 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810 goto error;
3811 }
3812
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003813 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003814 goto error;
3815
3816 {
3817 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003818 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003819 goto error;
3820 }
3821
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003822 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003823 goto error;
3824
3825 {
3826 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003827 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003828 goto error;
3829 }
3830
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003831 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003832 goto error;
3833
3834 {
3835 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003836 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003837 goto error;
3838 }
3839
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003840 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003841 goto error;
3842
3843 {
3844 struct tm tm;
3845 char datestr[48];
3846 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003847 "From: ", p->email_alert.from, "\r\n",
3848 "To: ", p->email_alert.to, "\r\n",
3849 "Date: ", datestr, "\r\n",
3850 "Subject: [HAproxy Alert] ", msg, "\r\n",
3851 "\r\n",
3852 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003853 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003854 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003855 NULL
3856 };
3857
3858 get_localtime(date.tv_sec, &tm);
3859
3860 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3861 goto error;
3862 }
3863
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003864 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003865 goto error;
3866 }
3867
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003868 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003869 goto error;
3870
3871 {
3872 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003873 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003874 goto error;
3875 }
3876
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003877 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003878 goto error;
3879
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003880 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003881 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003882 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003883 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003884 return 1;
3885
3886error:
3887 email_alert_free(alert);
3888 return 0;
3889}
3890
Christopher Faulet0108bb32017-10-20 21:34:32 +02003891static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003892{
3893 int i;
3894 struct mailer *mailer;
3895
3896 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3897 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003898 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003899 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003900 return;
3901 }
3902 }
3903
3904 return;
3905}
3906
3907/*
3908 * Send email alert if configured.
3909 */
Simon Horman64e34162015-02-06 11:11:57 +09003910void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003911{
3912 va_list argp;
3913 char buf[1024];
3914 int len;
3915 struct proxy *p = s->proxy;
3916
Christopher Faulet0108bb32017-10-20 21:34:32 +02003917 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003918 return;
3919
3920 va_start(argp, format);
3921 len = vsnprintf(buf, sizeof(buf), format, argp);
3922 va_end(argp);
3923
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003924 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003925 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003926 return;
3927 }
3928
Christopher Faulet0108bb32017-10-20 21:34:32 +02003929 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003930}
3931
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003932/*
3933 * Return value:
3934 * the port to be used for the health check
3935 * 0 in case no port could be found for the check
3936 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003937static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003938{
3939 int i = 0;
3940 struct server *srv = NULL;
3941
3942 srv = chk->server;
3943
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003944 /* by default, we use the health check port ocnfigured */
3945 if (chk->port > 0)
3946 return chk->port;
3947
3948 /* try to get the port from check_core.addr if check.port not set */
3949 i = get_host_port(&chk->addr);
3950 if (i > 0)
3951 return i;
3952
3953 /* try to get the port from server address */
3954 /* prevent MAPPORTS from working at this point, since checks could
3955 * not be performed in such case (MAPPORTS impose a relative ports
3956 * based on live traffic)
3957 */
3958 if (srv->flags & SRV_F_MAPPORTS)
3959 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01003960
3961 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003962 if (i > 0)
3963 return i;
3964
3965 return 0;
3966}
3967
Willy Tarreau172f5ce2018-11-26 11:21:50 +01003968REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003969
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003970static int check_proxy_tcpcheck(struct proxy *px)
3971{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003972 struct tcpcheck_rule *chk, *back;
3973 char *comment = NULL;
3974 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003975 int ret = 0;
3976
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003977 if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003978 goto out;
3979
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003980 if (!px->tcpcheck_rules.list) {
3981 px->tcpcheck_rules.list = calloc(1, sizeof(*px->tcpcheck_rules.list));
3982 if (!px->tcpcheck_rules.list) {
3983 ha_alert("config : proxy '%s': out of memory.\n", px->id);
3984 ret |= ERR_ALERT | ERR_FATAL;
3985 goto out;
3986 }
3987 LIST_INIT(px->tcpcheck_rules.list);
3988 }
3989
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003990 /* If there is no connect rule preceeding all send / expect rules, an
3991 * implicit one is inserted before all others
3992 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003993 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003994 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3995 chk = calloc(1, sizeof(*chk));
3996 if (!chk) {
3997 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3998 "(out of memory).\n", px->id);
3999 ret |= ERR_ALERT | ERR_FATAL;
4000 goto out;
4001 }
4002 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004003 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004004 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004005 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004006
4007 /* Now remove comment rules */
4008 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4009 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4010 free(comment);
4011 comment = NULL;
4012 }
4013
4014 prev_action = chk->action;
4015 switch (chk->action) {
4016 case TCPCHK_ACT_COMMENT:
4017 free(comment);
4018 comment = chk->comment;
4019 LIST_DEL(&chk->list);
4020 free(chk);
4021 break;
4022 case TCPCHK_ACT_CONNECT:
4023 if (!chk->comment && comment)
4024 chk->comment = strdup(comment);
4025 /* fall though */
4026 case TCPCHK_ACT_ACTION_KW:
4027 free(comment);
4028 comment = NULL;
4029 break;
4030 case TCPCHK_ACT_SEND:
4031 case TCPCHK_ACT_EXPECT:
4032 if (!chk->comment && comment)
4033 chk->comment = strdup(comment);
4034 break;
4035 }
4036 }
4037 free(comment);
4038 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004039
4040 out:
4041 return ret;
4042}
4043
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004044static int init_srv_check(struct server *srv)
4045{
4046 const char *err;
4047 struct tcpcheck_rule *r;
4048 int ret = 0;
4049
4050 if (!srv->do_check)
4051 goto out;
4052
4053
4054 /* If neither a port nor an addr was specified and no check transport
4055 * layer is forced, then the transport layer used by the checks is the
4056 * same as for the production traffic. Otherwise we use raw_sock by
4057 * default, unless one is specified.
4058 */
4059 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4060 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4061 srv->check.use_ssl = srv->use_ssl;
4062 srv->check.xprt = srv->xprt;
4063 }
4064 else if (srv->check.use_ssl == 1)
4065 srv->check.xprt = xprt_get(XPRT_SSL);
4066
4067 srv->check.send_proxy |= (srv->pp_opts);
4068 }
4069
4070 /* validate <srv> server health-check settings */
4071
4072 /* We need at least a service port, a check port or the first tcp-check
4073 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4074 */
4075 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4076 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4077 goto init;
4078
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004079 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004080 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4081 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4082 ret |= ERR_ALERT | ERR_ABORT;
4083 goto out;
4084 }
4085
4086 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004087 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004088 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004089 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4090 "nor tcp_check rule 'connect' with port information.\n",
4091 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4092 ret |= ERR_ALERT | ERR_ABORT;
4093 goto out;
4094 }
4095
4096 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004097 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004098 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004099 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4100 "and a tcp_check rule 'connect' with no port information.\n",
4101 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4102 ret |= ERR_ALERT | ERR_ABORT;
4103 goto out;
4104 }
4105 }
4106
4107 init:
4108 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4109 if (err) {
4110 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4111 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4112 ret |= ERR_ALERT | ERR_ABORT;
4113 goto out;
4114 }
4115 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4116 global.maxsock++;
4117
4118 out:
4119 return ret;
4120}
4121
4122static int init_srv_agent_check(struct server *srv)
4123{
4124 const char *err;
4125 int ret = 0;
4126
4127 if (!srv->do_agent)
4128 goto out;
4129
4130 err = init_check(&srv->agent, PR_O2_LB_AGENT_CHK);
4131 if (err) {
4132 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4133 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4134 ret |= ERR_ALERT | ERR_ABORT;
4135 goto out;
4136 }
4137
4138 if (!srv->agent.inter)
4139 srv->agent.inter = srv->check.inter;
4140
4141 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4142 global.maxsock++;
4143
4144 out:
4145 return ret;
4146}
4147
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004148void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004149{
4150 struct tcpcheck_rule *chk, *back;
4151
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004152 if (!px->tcpcheck_rules.list || (px->tcpcheck_rules.flags & TCPCHK_RULES_SHARED))
4153 goto end;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004154
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004155 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004156 LIST_DEL(&chk->list);
4157 free_tcpcheck(chk, 0);
4158 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004159 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004160 free(px->tcpcheck_rules.list);
4161
4162 end:
4163 px->tcpcheck_rules.flags = 0;
4164 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004165}
4166
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004167static void deinit_srv_check(struct server *srv)
4168{
4169 if (srv->do_check)
4170 free_check(&srv->check);
4171}
4172
4173
4174static void deinit_srv_agent_check(struct server *srv)
4175{
4176 if (srv->do_agent)
4177 free_check(&srv->agent);
4178 free(srv->agent.send_string);
4179}
4180
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004181static void deinit_tcpchecks()
4182{
4183 struct tcpcheck_ruleset *rs, *rsb;
4184 struct tcpcheck_rule *r, *rb;
4185
4186 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4187 LIST_DEL(&rs->list);
4188 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4189 LIST_DEL(&r->list);
4190 free_tcpcheck(r, 0);
4191 }
4192 free(rs->name);
4193 free(rs);
4194 }
4195}
4196
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004197
4198REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004199REGISTER_POST_SERVER_CHECK(init_srv_check);
4200REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4201
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004202REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004203REGISTER_SERVER_DEINIT(deinit_srv_check);
4204REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004205REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004206
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004207/* extracts check payload at a fixed position and length */
4208static int
4209smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4210{
4211 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4212 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4213 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4214 struct buffer *buf;
4215
4216 if (!srv || !srv->do_check)
4217 return 0;
4218
4219 buf = &srv->check.bi;
4220 if (buf_offset > b_data(buf))
4221 goto no_match;
4222 if (buf_offset + buf_size > b_data(buf))
4223 buf_size = 0;
4224
4225 /* init chunk as read only */
4226 smp->data.type = SMP_T_STR;
4227 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4228 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4229
4230 return 1;
4231
4232 no_match:
4233 smp->flags = 0;
4234 return 0;
4235}
4236
4237static struct sample_fetch_kw_list smp_kws = {ILH, {
4238 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4239 { /* END */ },
4240}};
4241
4242INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4243
4244
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004245struct action_kw_list tcp_check_keywords = {
4246 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4247};
4248
4249/* Return the struct action_kw associated to a keyword */
4250static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4251{
4252 return action_lookup(&tcp_check_keywords.list, kw);
4253}
4254
4255static void action_kw_tcp_check_build_list(struct buffer *chk)
4256{
4257 action_build_list(&tcp_check_keywords.list, chk);
4258}
4259
4260/* Create a tcp-check rule resulting from parsing a custom keyword. */
4261static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004262 struct list *rules, struct action_kw *kw,
4263 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004264{
4265 struct tcpcheck_rule *chk = NULL;
4266 struct act_rule *actrule = NULL;
4267
4268 actrule = calloc(1, sizeof(*actrule));
4269 if (!actrule) {
4270 memprintf(errmsg, "out of memory");
4271 goto error;
4272 }
4273 actrule->kw = kw;
4274 actrule->from = ACT_F_TCP_CHK;
4275
4276 cur_arg++;
4277 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4278 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4279 goto error;
4280 }
4281
4282 chk = calloc(1, sizeof(*chk));
4283 if (!chk) {
4284 memprintf(errmsg, "out of memory");
4285 goto error;
4286 }
4287 chk->action = TCPCHK_ACT_ACTION_KW;
4288 chk->action_kw.rule = actrule;
4289 return chk;
4290
4291 error:
4292 free(actrule);
4293 return NULL;
4294}
4295
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004296static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004297 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004298{
4299 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004300 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004301 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004302 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004303 unsigned short conn_opts = 0;
4304 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004305 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004306
4307 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004308 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004309 break;
4310 }
4311 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004312 memprintf(errmsg, "first step MUST also be a 'connect', "
4313 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4314 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004315 goto error;
4316 }
4317
4318 cur_arg++;
4319 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004320 if (strcmp(args[cur_arg], "default") == 0)
4321 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004322 else if (strcmp(args[cur_arg], "addr") == 0) {
4323 int port1, port2;
4324 struct protocol *proto;
4325
4326 if (!*(args[cur_arg+1])) {
4327 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4328 goto error;
4329 }
4330
4331 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4332 if (!sk) {
4333 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4334 goto error;
4335 }
4336
4337 proto = protocol_by_family(sk->ss_family);
4338 if (!proto || !proto->connect) {
4339 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4340 args[cur_arg]);
4341 goto error;
4342 }
4343
4344 if (port1 != port2) {
4345 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4346 args[cur_arg], args[cur_arg+1]);
4347 goto error;
4348 }
4349
4350 cur_arg++;
4351 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004352 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004353 const char *p, *end;
4354
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004355 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004356 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004357 goto error;
4358 }
4359 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004360
4361 port = 0;
4362 release_sample_expr(port_expr);
4363 p = args[cur_arg]; end = p + strlen(p);
4364 port = read_uint(&p, end);
4365 if (p != end) {
4366 int idx = 0;
4367
4368 px->conf.args.ctx = ARGC_SRV;
4369 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4370 file, line, errmsg, &px->conf.args, NULL);
4371
4372 if (!port_expr) {
4373 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4374 goto error;
4375 }
4376 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4377 memprintf(errmsg, "error detected while parsing port expression : "
4378 " fetch method '%s' extracts information from '%s', "
4379 "none of which is available here.\n",
4380 args[cur_arg], sample_src_names(port_expr->fetch->use));
4381 goto error;
4382 }
4383 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4384 }
4385 else if (port > 65535 || port < 1) {
4386 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4387 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004388 goto error;
4389 }
4390 }
4391 else if (strcmp(args[cur_arg], "comment") == 0) {
4392 if (!*(args[cur_arg+1])) {
4393 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4394 goto error;
4395 }
4396 cur_arg++;
4397 free(comment);
4398 comment = strdup(args[cur_arg]);
4399 if (!comment) {
4400 memprintf(errmsg, "out of memory");
4401 goto error;
4402 }
4403 }
4404 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4405 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004406 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4407 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004408 else if (strcmp(args[cur_arg], "linger") == 0)
4409 conn_opts |= TCPCHK_OPT_LINGER;
4410#ifdef USE_OPENSSL
4411 else if (strcmp(args[cur_arg], "ssl") == 0) {
4412 px->options |= PR_O_TCPCHK_SSL;
4413 conn_opts |= TCPCHK_OPT_SSL;
4414 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004415 else if (strcmp(args[cur_arg], "sni") == 0) {
4416 if (!*(args[cur_arg+1])) {
4417 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4418 goto error;
4419 }
4420 cur_arg++;
4421 free(sni);
4422 sni = strdup(args[cur_arg]);
4423 if (!sni) {
4424 memprintf(errmsg, "out of memory");
4425 goto error;
4426 }
4427 }
Christopher Faulet98572322020-03-30 13:16:44 +02004428 else if (strcmp(args[cur_arg], "alpn") == 0) {
4429#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4430 free(alpn);
4431 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4432 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4433 goto error;
4434 }
4435 cur_arg++;
4436#else
4437 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4438 goto error;
4439#endif
4440 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004441#endif /* USE_OPENSSL */
4442
4443 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004444 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004445#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004446 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004447#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004448 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004449 args[cur_arg]);
4450 goto error;
4451 }
4452 cur_arg++;
4453 }
4454
4455 chk = calloc(1, sizeof(*chk));
4456 if (!chk) {
4457 memprintf(errmsg, "out of memory");
4458 goto error;
4459 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004460 chk->action = TCPCHK_ACT_CONNECT;
4461 chk->comment = comment;
4462 chk->connect.port = port;
4463 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004464 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004465 chk->connect.alpn = alpn;
4466 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004467 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004468 if (sk)
4469 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004470 return chk;
4471
4472 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004473 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004474 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004475 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004476 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004477 return NULL;
4478}
4479
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004480static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004481 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004482{
4483 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004484 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004485 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004486
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004487 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004488 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004489 memprintf(errmsg, "'%s' expects a %s as argument",
4490 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004491 goto error;
4492 }
4493
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004494 data = args[cur_arg+1];
4495
4496 cur_arg += 2;
4497 while (*(args[cur_arg])) {
4498 if (strcmp(args[cur_arg], "comment") == 0) {
4499 if (!*(args[cur_arg+1])) {
4500 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4501 goto error;
4502 }
4503 cur_arg++;
4504 free(comment);
4505 comment = strdup(args[cur_arg]);
4506 if (!comment) {
4507 memprintf(errmsg, "out of memory");
4508 goto error;
4509 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004510 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004511 else if (strcmp(args[cur_arg], "log-format") == 0) {
4512 if (type == TCPCHK_SEND_BINARY)
4513 type = TCPCHK_SEND_BINARY_LF;
4514 else if (type == TCPCHK_SEND_STRING)
4515 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004516 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004517 else {
4518 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4519 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004520 goto error;
4521 }
4522 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004523 }
4524
4525 chk = calloc(1, sizeof(*chk));
4526 if (!chk) {
4527 memprintf(errmsg, "out of memory");
4528 goto error;
4529 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004530 chk->action = TCPCHK_ACT_SEND;
4531 chk->comment = comment;
4532 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004533
4534 switch (chk->send.type) {
4535 case TCPCHK_SEND_STRING:
4536 chk->send.data = ist2(strdup(data), strlen(data));
4537 if (!isttest(chk->send.data)) {
4538 memprintf(errmsg, "out of memory");
4539 goto error;
4540 }
4541 break;
4542 case TCPCHK_SEND_BINARY:
4543 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4544 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4545 goto error;
4546 }
4547 break;
4548 case TCPCHK_SEND_STRING_LF:
4549 case TCPCHK_SEND_BINARY_LF:
4550 LIST_INIT(&chk->send.fmt);
4551 px->conf.args.ctx = ARGC_SRV;
4552 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4553 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4554 goto error;
4555 }
4556 break;
4557 case TCPCHK_SEND_UNDEF:
4558 goto error;
4559 }
4560
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004561 return chk;
4562
4563 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004564 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004565 free(comment);
4566 return NULL;
4567}
4568
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004569static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4570 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004571{
4572 struct tcpcheck_rule *chk = NULL;
4573 char *comment = NULL;
4574
4575 if (!*(args[cur_arg+1])) {
4576 memprintf(errmsg, "expects a string as argument");
4577 goto error;
4578 }
4579 cur_arg++;
4580 comment = strdup(args[cur_arg]);
4581 if (!comment) {
4582 memprintf(errmsg, "out of memory");
4583 goto error;
4584 }
4585
4586 chk = calloc(1, sizeof(*chk));
4587 if (!chk) {
4588 memprintf(errmsg, "out of memory");
4589 goto error;
4590 }
4591 chk->action = TCPCHK_ACT_COMMENT;
4592 chk->comment = comment;
4593 return chk;
4594
4595 error:
4596 free(comment);
4597 return NULL;
4598}
4599
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004600static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4601 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004602{
4603 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004604 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004605 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004606 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004607 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4608 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004609 long min_recv = -1;
4610 int inverse = 0, with_capture = 0;
4611
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004612 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004613 if (!*(args[cur_arg+1])) {
4614 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004615 goto error;
4616 }
4617
4618 cur_arg++;
4619 while (*(args[cur_arg])) {
4620 int in_pattern = 0;
4621
4622 rescan:
4623 if (strcmp(args[cur_arg], "min-recv") == 0) {
4624 if (in_pattern) {
4625 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4626 goto error;
4627 }
4628 if (!*(args[cur_arg+1])) {
4629 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4630 goto error;
4631 }
4632 /* Use an signed integer here because of chksize */
4633 cur_arg++;
4634 min_recv = atol(args[cur_arg]);
4635 if (min_recv < -1 || min_recv > INT_MAX) {
4636 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4637 goto error;
4638 }
4639 }
4640 else if (*(args[cur_arg]) == '!') {
4641 in_pattern = 1;
4642 while (*(args[cur_arg]) == '!') {
4643 inverse = !inverse;
4644 args[cur_arg]++;
4645 }
4646 if (!*(args[cur_arg]))
4647 cur_arg++;
4648 goto rescan;
4649 }
4650 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4651 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4652 if (type != TCPCHK_EXPECT_UNDEF) {
4653 memprintf(errmsg, "only on pattern expected");
4654 goto error;
4655 }
4656 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4657 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4658 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4659
4660 if (!*(args[cur_arg+1])) {
4661 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4662 goto error;
4663 }
4664 cur_arg++;
4665 pattern = args[cur_arg];
4666 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004667 else if (strcmp(args[cur_arg], "custom") == 0) {
4668 if (in_pattern) {
4669 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4670 goto error;
4671 }
4672 if (type != TCPCHK_EXPECT_UNDEF) {
4673 memprintf(errmsg, "only on pattern expected");
4674 goto error;
4675 }
4676 type = TCPCHK_EXPECT_CUSTOM;
4677 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004678 else if (strcmp(args[cur_arg], "comment") == 0) {
4679 if (in_pattern) {
4680 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4681 goto error;
4682 }
4683 if (!*(args[cur_arg+1])) {
4684 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4685 goto error;
4686 }
4687 cur_arg++;
4688 free(comment);
4689 comment = strdup(args[cur_arg]);
4690 if (!comment) {
4691 memprintf(errmsg, "out of memory");
4692 goto error;
4693 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004694 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004695 else if (strcmp(args[cur_arg], "on-success") == 0) {
4696 if (in_pattern) {
4697 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4698 goto error;
4699 }
4700 if (!*(args[cur_arg+1])) {
4701 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4702 goto error;
4703 }
4704 cur_arg++;
4705 free(on_success_msg);
4706 on_success_msg = strdup(args[cur_arg]);
4707 if (!on_success_msg) {
4708 memprintf(errmsg, "out of memory");
4709 goto error;
4710 }
4711 }
4712 else if (strcmp(args[cur_arg], "on-error") == 0) {
4713 if (in_pattern) {
4714 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4715 goto error;
4716 }
4717 if (!*(args[cur_arg+1])) {
4718 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4719 goto error;
4720 }
4721 cur_arg++;
4722 free(on_error_msg);
4723 on_error_msg = strdup(args[cur_arg]);
4724 if (!on_error_msg) {
4725 memprintf(errmsg, "out of memory");
4726 goto error;
4727 }
4728 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004729 else if (strcmp(args[cur_arg], "error-status") == 0) {
4730 if (in_pattern) {
4731 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4732 goto error;
4733 }
4734 if (!*(args[cur_arg+1])) {
4735 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4736 goto error;
4737 }
4738 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4739 err_st = HCHK_STATUS_L7RSP;
4740 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4741 err_st = HCHK_STATUS_L7STS;
4742 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4743 err_st = HCHK_STATUS_L6RSP;
4744 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4745 err_st = HCHK_STATUS_L4CON;
4746 else {
4747 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4748 args[cur_arg], args[cur_arg+1]);
4749 goto error;
4750 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004751 cur_arg++;
4752 }
4753 else if (strcmp(args[cur_arg], "status-code") == 0) {
4754 int idx = 0;
4755
4756 if (in_pattern) {
4757 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4758 goto error;
4759 }
4760 if (!*(args[cur_arg+1])) {
4761 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4762 goto error;
4763 }
4764
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004765 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004766 release_sample_expr(status_expr);
4767 px->conf.args.ctx = ARGC_SRV;
4768 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4769 file, line, errmsg, &px->conf.args, NULL);
4770 if (!status_expr) {
4771 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4772 goto error;
4773 }
4774 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4775 memprintf(errmsg, "error detected while parsing status-code expression : "
4776 " fetch method '%s' extracts information from '%s', "
4777 "none of which is available here.\n",
4778 args[cur_arg], sample_src_names(status_expr->fetch->use));
4779 goto error;
4780 }
4781 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004782 }
4783 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4784 if (in_pattern) {
4785 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4786 goto error;
4787 }
4788 if (!*(args[cur_arg+1])) {
4789 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4790 goto error;
4791 }
4792 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4793 tout_st = HCHK_STATUS_L7TOUT;
4794 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4795 tout_st = HCHK_STATUS_L6TOUT;
4796 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4797 tout_st = HCHK_STATUS_L4TOUT;
4798 else {
4799 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4800 args[cur_arg], args[cur_arg+1]);
4801 goto error;
4802 }
4803 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004804 }
4805 else {
4806 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4807 " or comment but got '%s' as argument.", args[cur_arg]);
4808 goto error;
4809 }
4810
4811 cur_arg++;
4812 }
4813
4814 if (comment) {
4815 char *p = comment;
4816
4817 while (*p) {
4818 if (*p == '\\') {
4819 p++;
4820 if (!*p || !isdigit((unsigned char)*p) ||
4821 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4822 memprintf(errmsg, "invalid backreference in 'comment' argument");
4823 goto error;
4824 }
4825 with_capture = 1;
4826 }
4827 p++;
4828 }
4829 if (with_capture && !inverse)
4830 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4831 }
4832
4833 chk = calloc(1, sizeof(*chk));
4834 if (!chk) {
4835 memprintf(errmsg, "out of memory");
4836 goto error;
4837 }
4838 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004839 LIST_INIT(&chk->expect.onerror_fmt);
4840 LIST_INIT(&chk->expect.onsuccess_fmt);
4841 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004842 chk->expect.type = type;
4843 chk->expect.min_recv = min_recv;
4844 chk->expect.inverse = inverse;
4845 chk->expect.with_capture = with_capture;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004846 chk->expect.err_status = err_st;
4847 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004848 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004849
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004850 if (on_success_msg) {
4851 px->conf.args.ctx = ARGC_SRV;
4852 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4853 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4854 goto error;
4855 }
4856 free(on_success_msg);
4857 on_success_msg = NULL;
4858 }
4859 if (on_error_msg) {
4860 px->conf.args.ctx = ARGC_SRV;
4861 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4862 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4863 goto error;
4864 }
4865 free(on_error_msg);
4866 on_error_msg = NULL;
4867 }
4868
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004869 switch (chk->expect.type) {
4870 case TCPCHK_EXPECT_STRING:
4871 chk->expect.string = strdup(pattern);
4872 chk->expect.length = strlen(pattern);
4873 if (!chk->expect.string) {
4874 memprintf(errmsg, "out of memory");
4875 goto error;
4876 }
4877 break;
4878 case TCPCHK_EXPECT_BINARY:
4879 if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
4880 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4881 goto error;
4882 }
4883 case TCPCHK_EXPECT_REGEX:
4884 case TCPCHK_EXPECT_REGEX_BINARY:
4885 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
4886 if (!chk->expect.regex)
4887 goto error;
4888 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004889 case TCPCHK_EXPECT_CUSTOM:
4890 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4891 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004892 case TCPCHK_EXPECT_UNDEF:
4893 free(chk);
4894 memprintf(errmsg, "pattern not found");
4895 goto error;
4896 }
4897
4898 /* All tcp-check expect points back to the first inverse expect rule in
4899 * a chain of one or more expect rule, potentially itself.
4900 */
4901 chk->expect.head = chk;
4902 list_for_each_entry_rev(prev_check, rules, list) {
4903 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4904 if (prev_check->expect.inverse)
4905 chk->expect.head = prev_check;
4906 continue;
4907 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01004908 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004909 break;
4910 }
4911 return chk;
4912
4913 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004914 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004915 free(str);
4916 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004917 free(on_success_msg);
4918 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004919 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004920 return NULL;
4921}
4922
4923/* Parses the "tcp-check" proxy keyword */
4924static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
4925 struct proxy *defpx, const char *file, int line,
4926 char **errmsg)
4927{
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004928 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004929 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01004930 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004931
4932 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
4933 ret = 1;
4934
4935 if (curpx == defpx) {
4936 memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
4937 goto error;
4938 }
4939
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004940 if (rules->flags & TCPCHK_RULES_DEF) {
4941 /* Only shared ruleset can be inherited from the default section */
4942 rules->flags = 0;
4943 rules->list = NULL;
4944 }
4945 if (rules->list && (rules->flags & TCPCHK_RULES_SHARED)) {
4946 memprintf(errmsg, "%s : A shared tcp-check ruleset already configured.", args[0]);
4947 goto error;
4948 }
4949
4950 if (!rules->list) {
4951 rules->list = calloc(1, sizeof(*rules->list));
4952 if (!rules->list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004953 memprintf(errmsg, "%s : out of memory.", args[0]);
4954 goto error;
4955 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004956 LIST_INIT(rules->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004957 }
4958
Gaetan Rivet5301b012020-02-25 17:19:17 +01004959 index = 0;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004960 if (!LIST_ISEMPTY(rules->list)) {
4961 chk = LIST_PREV(rules->list, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01004962 index = chk->index + 1;
4963 }
4964
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004965 cur_arg = 1;
4966 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004967 chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004968 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004969 chk = parse_tcpcheck_send(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004970 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004971 chk = parse_tcpcheck_expect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004972 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004973 chk = parse_tcpcheck_comment(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004974 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004975 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4976
4977 if (!kw) {
4978 action_kw_tcp_check_build_list(&trash);
4979 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
4980 "%s%s. but got '%s'",
4981 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4982 goto error;
4983 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004984 chk = parse_tcpcheck_action(args, cur_arg, curpx, rules->list, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004985 }
4986
4987 if (!chk) {
4988 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4989 goto error;
4990 }
4991 ret = (*errmsg != NULL); /* Handle warning */
4992
4993 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01004994 chk->index = index;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004995 LIST_ADDQ(rules->list, &chk->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004996 return ret;
4997
4998 error:
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004999 deinit_proxy_tcpcheck(curpx);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005000 return -1;
5001}
5002
Christopher Faulet33f05df2020-04-01 11:08:50 +02005003
5004static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
5005{
5006 struct tcpcheck_ruleset *rs;
5007
5008 list_for_each_entry(rs, &tcpchecks_list, list) {
5009 if (strcmp(rs->name, name) == 0)
5010 return rs;
5011 }
5012 return NULL;
5013}
5014
5015static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
5016{
5017 struct tcpcheck_ruleset *rs;
5018
5019 rs = calloc(1, sizeof(*rs));
5020 if (rs == NULL)
5021 return NULL;
5022
5023 rs->name = strdup(name);
5024 if (rs->name == NULL) {
5025 free(rs);
5026 return NULL;
5027 }
5028
5029 LIST_INIT(&rs->list);
5030 LIST_INIT(&rs->rules);
5031 LIST_ADDQ(&tcpchecks_list, &rs->list);
5032 return rs;
5033}
5034
5035static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
5036{
5037 struct tcpcheck_rule *r, *rb;
5038 if (!rs)
5039 return;
5040
5041 LIST_DEL(&rs->list);
5042 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5043 LIST_DEL(&r->list);
5044 free_tcpcheck(r, 0);
5045 }
5046 free(rs->name);
5047 free(rs);
5048}
5049
5050
5051/* Parses the "option redis-check" proxy keyword */
5052int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5053 const char *file, int line)
5054{
5055 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5056 static char *redis_res = "+PONG\r\n";
5057
5058 struct tcpcheck_ruleset *rs = NULL;
5059 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5060 struct tcpcheck_rule *chk;
5061 char *errmsg = NULL;
5062 int err_code = 0;
5063
5064 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5065 err_code |= ERR_WARN;
5066
5067 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5068 goto out;
5069
5070 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5071 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5072 file, line);
5073 err_code |= ERR_ALERT | ERR_FATAL;
5074 goto out;
5075 }
5076
5077 curpx->options2 &= ~PR_O2_CHK_ANY;
5078 curpx->options2 |= PR_O2_TCPCHK_CHK;
5079
5080 free_tcpcheck_vars(&rules->preset_vars);
5081 rules->list = NULL;
5082 rules->flags = 0;
5083
5084 rs = tcpcheck_ruleset_lookup("*redis-check");
5085 if (rs)
5086 goto ruleset_found;
5087
5088 rs = tcpcheck_ruleset_create("*redis-check");
5089 if (rs == NULL) {
5090 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5091 goto error;
5092 }
5093
5094 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5095 1, curpx, &rs->rules, file, line, &errmsg);
5096 if (!chk) {
5097 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5098 goto error;
5099 }
5100 chk->index = 0;
5101 LIST_ADDQ(&rs->rules, &chk->list);
5102
5103 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5104 "error-status", "L7STS",
5105 "on-error", "%[check.payload(),cut_crlf]",
5106 "on-success", "Redis server is ok",
5107 ""},
5108 1, curpx, &rs->rules, file, line, &errmsg);
5109 if (!chk) {
5110 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5111 goto error;
5112 }
5113 chk->index = 1;
5114 LIST_ADDQ(&rs->rules, &chk->list);
5115
5116 LIST_ADDQ(&tcpchecks_list, &rs->list);
5117
5118 ruleset_found:
5119 rules->list = &rs->rules;
5120 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_REDIS_CHK);
5121
5122 out:
5123 free(errmsg);
5124 return err_code;
5125
5126 error:
5127 tcpcheck_ruleset_release(rs);
5128 err_code |= ERR_ALERT | ERR_FATAL;
5129 goto out;
5130}
5131
Christopher Faulet811f78c2020-04-01 11:10:27 +02005132
5133/* Parses the "option ssl-hello-chk" proxy keyword */
5134int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5135 const char *file, int line)
5136{
5137 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5138 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5139 *
5140 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5141 */
5142 static char sslv3_client_hello[] = {
5143 "16" /* ContentType : 0x16 = Hanshake */
5144 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5145 "0079" /* ContentLength : 0x79 bytes after this one */
5146 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5147 "000075" /* HandshakeLength : 0x75 bytes after this one */
5148 "0300" /* Hello Version : 0x0300 = v3 */
5149 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5150 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5151 "00" /* Session ID length : empty (no session ID) */
5152 "004E" /* Cipher Suite Length : 78 bytes after this one */
5153 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5154 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5155 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5156 "000D" "000E" "000F" "0010" /* various bit lengths, */
5157 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5158 "0015" "0016" "0017" "0018"
5159 "0019" "001A" "001B" "002F"
5160 "0030" "0031" "0032" "0033"
5161 "0034" "0035" "0036" "0037"
5162 "0038" "0039" "003A"
5163 "01" /* Compression Length : 0x01 = 1 byte for types */
5164 "00" /* Compression Type : 0x00 = NULL compression */
5165 };
5166
5167 struct tcpcheck_ruleset *rs = NULL;
5168 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5169 struct tcpcheck_rule *chk;
5170 char *errmsg = NULL;
5171 int err_code = 0;
5172
5173 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5174 err_code |= ERR_WARN;
5175
5176 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5177 goto out;
5178
5179 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5180 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5181 file, line);
5182 err_code |= ERR_ALERT | ERR_FATAL;
5183 goto out;
5184 }
5185
5186 curpx->options2 &= ~PR_O2_CHK_ANY;
5187 curpx->options2 |= PR_O2_TCPCHK_CHK;
5188
5189 free_tcpcheck_vars(&rules->preset_vars);
5190 rules->list = NULL;
5191 rules->flags = 0;
5192
5193 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5194 if (rs)
5195 goto ruleset_found;
5196
5197 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5198 if (rs == NULL) {
5199 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5200 goto error;
5201 }
5202
5203 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5204 1, curpx, &rs->rules, file, line, &errmsg);
5205 if (!chk) {
5206 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5207 goto error;
5208 }
5209 chk->index = 0;
5210 LIST_ADDQ(&rs->rules, &chk->list);
5211
5212 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
5213 "min-recv", "5",
5214 "error-status", "L6RSP", "tout-status", "L6TOUT",
5215 ""},
5216 1, curpx, &rs->rules, file, line, &errmsg);
5217 if (!chk) {
5218 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5219 goto error;
5220 }
5221 chk->index = 1;
5222 LIST_ADDQ(&rs->rules, &chk->list);
5223
5224 LIST_ADDQ(&tcpchecks_list, &rs->list);
5225
5226 ruleset_found:
5227 rules->list = &rs->rules;
5228 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SSL3_CHK);
5229
5230 out:
5231 free(errmsg);
5232 return err_code;
5233
5234 error:
5235 tcpcheck_ruleset_release(rs);
5236 err_code |= ERR_ALERT | ERR_FATAL;
5237 goto out;
5238}
5239
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005240/* Parses the "option smtpchk" proxy keyword */
5241int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5242 const char *file, int line)
5243{
5244 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5245
5246 struct tcpcheck_ruleset *rs = NULL;
5247 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5248 struct tcpcheck_rule *chk;
5249 struct tcpcheck_var *var = NULL;
5250 char *cmd = NULL, *errmsg = NULL;
5251 int err_code = 0;
5252
5253 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5254 err_code |= ERR_WARN;
5255
5256 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5257 goto out;
5258
5259 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5260 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5261 file, line);
5262 err_code |= ERR_ALERT | ERR_FATAL;
5263 goto out;
5264 }
5265
5266 curpx->options2 &= ~PR_O2_CHK_ANY;
5267 curpx->options2 |= PR_O2_TCPCHK_CHK;
5268
5269 free_tcpcheck_vars(&rules->preset_vars);
5270 rules->list = NULL;
5271 rules->flags = 0;
5272
5273 cur_arg += 2;
5274 if (*args[cur_arg] && *args[cur_arg+1] &&
5275 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5276 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5277 if (cmd)
5278 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5279 }
5280 else {
5281 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5282 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5283 cmd = strdup("HELO localhost");
5284 }
5285
5286 var = tcpcheck_var_create("check.smtp_cmd");
5287 if (cmd == NULL || var == NULL) {
5288 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5289 goto error;
5290 }
5291 var->data.type = SMP_T_STR;
5292 var->data.u.str.area = cmd;
5293 var->data.u.str.data = strlen(cmd);
5294 LIST_INIT(&var->list);
5295 LIST_ADDQ(&rules->preset_vars, &var->list);
5296 cmd = NULL;
5297 var = NULL;
5298
5299 rs = tcpcheck_ruleset_lookup("*smtp-check");
5300 if (rs)
5301 goto ruleset_found;
5302
5303 rs = tcpcheck_ruleset_create("*smtp-check");
5304 if (rs == NULL) {
5305 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5306 goto error;
5307 }
5308
5309 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5310 1, curpx, &rs->rules, file, line, &errmsg);
5311 if (!chk) {
5312 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5313 goto error;
5314 }
5315 chk->index = 0;
5316 LIST_ADDQ(&rs->rules, &chk->list);
5317
5318 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5319 "min-recv", "4",
5320 "error-status", "L7RSP",
5321 "on-error", "%[check.payload(),cut_crlf]",
5322 ""},
5323 1, curpx, &rs->rules, file, line, &errmsg);
5324 if (!chk) {
5325 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5326 goto error;
5327 }
5328 chk->index = 1;
5329 LIST_ADDQ(&rs->rules, &chk->list);
5330
5331 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5332 "min-recv", "4",
5333 "error-status", "L7STS",
5334 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5335 "status-code", "check.payload(0,3)",
5336 ""},
5337 1, curpx, &rs->rules, file, line, &errmsg);
5338 if (!chk) {
5339 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5340 goto error;
5341 }
5342 chk->index = 2;
5343 LIST_ADDQ(&rs->rules, &chk->list);
5344
5345 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5346 1, curpx, &rs->rules, file, line, &errmsg);
5347 if (!chk) {
5348 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5349 goto error;
5350 }
5351 chk->index = 3;
5352 LIST_ADDQ(&rs->rules, &chk->list);
5353
5354 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5355 "min-recv", "4",
5356 "error-status", "L7STS",
5357 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5358 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5359 "status-code", "check.payload(0,3)",
5360 ""},
5361 1, curpx, &rs->rules, file, line, &errmsg);
5362 if (!chk) {
5363 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5364 goto error;
5365 }
5366 chk->index = 4;
5367 LIST_ADDQ(&rs->rules, &chk->list);
5368
5369 LIST_ADDQ(&tcpchecks_list, &rs->list);
5370
5371 ruleset_found:
5372 rules->list = &rs->rules;
5373 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SMTP_CHK);
5374
5375 out:
5376 free(errmsg);
5377 return err_code;
5378
5379 error:
5380 free(cmd);
5381 free(var);
5382 free_tcpcheck_vars(&rules->preset_vars);
5383 tcpcheck_ruleset_release(rs);
5384 err_code |= ERR_ALERT | ERR_FATAL;
5385 goto out;
5386}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005387
Christopher Fauletce355072020-04-02 11:44:39 +02005388/* Parses the "option pgsql-check" proxy keyword */
5389int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5390 const char *file, int line)
5391{
5392 static char pgsql_req[] = {
5393 "%[var(check.plen),htonl,hex]" /* The packet length*/
5394 "00030000" /* the version 3.0 */
5395 "7573657200" /* "user" key */
5396 "%[var(check.username),hex]00" /* the username */
5397 "00"
5398 };
5399
5400 struct tcpcheck_ruleset *rs = NULL;
5401 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5402 struct tcpcheck_rule *chk;
5403 struct tcpcheck_var *var = NULL;
5404 char *user = NULL, *errmsg = NULL;
5405 size_t packetlen = 0;
5406 int err_code = 0;
5407
5408 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5409 err_code |= ERR_WARN;
5410
5411 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5412 goto out;
5413
5414 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5415 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5416 file, line);
5417 err_code |= ERR_ALERT | ERR_FATAL;
5418 goto out;
5419 }
5420
5421
5422 curpx->options2 &= ~PR_O2_CHK_ANY;
5423 curpx->options2 |= PR_O2_TCPCHK_CHK;
5424
5425 free_tcpcheck_vars(&rules->preset_vars);
5426 rules->list = NULL;
5427 rules->flags = 0;
5428
5429 cur_arg += 2;
5430 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5431 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5432 file, line, args[0], args[1]);
5433 goto error;
5434 }
5435 if (strcmp(args[cur_arg], "user") == 0) {
5436 packetlen = 15 + strlen(args[cur_arg+1]);
5437 user = strdup(args[cur_arg+1]);
5438
5439 var = tcpcheck_var_create("check.username");
5440 if (user == NULL || var == NULL) {
5441 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5442 goto error;
5443 }
5444 var->data.type = SMP_T_STR;
5445 var->data.u.str.area = user;
5446 var->data.u.str.data = strlen(user);
5447 LIST_INIT(&var->list);
5448 LIST_ADDQ(&rules->preset_vars, &var->list);
5449 user = NULL;
5450 var = NULL;
5451
5452 var = tcpcheck_var_create("check.plen");
5453 if (var == NULL) {
5454 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5455 goto error;
5456 }
5457 var->data.type = SMP_T_SINT;
5458 var->data.u.sint = packetlen;
5459 LIST_INIT(&var->list);
5460 LIST_ADDQ(&rules->preset_vars, &var->list);
5461 var = NULL;
5462 }
5463 else {
5464 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5465 file, line, args[0], args[1]);
5466 goto error;
5467 }
5468
5469 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5470 if (rs)
5471 goto ruleset_found;
5472
5473 rs = tcpcheck_ruleset_create("*pgsql-check");
5474 if (rs == NULL) {
5475 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5476 goto error;
5477 }
5478
5479 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5480 1, curpx, &rs->rules, file, line, &errmsg);
5481 if (!chk) {
5482 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5483 goto error;
5484 }
5485 chk->index = 0;
5486 LIST_ADDQ(&rs->rules, &chk->list);
5487
5488 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5489 1, curpx, &rs->rules, file, line, &errmsg);
5490 if (!chk) {
5491 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5492 goto error;
5493 }
5494 chk->index = 1;
5495 LIST_ADDQ(&rs->rules, &chk->list);
5496
5497 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5498 "min-recv", "5",
5499 "error-status", "L7RSP",
5500 "on-error", "%[check.payload(6,0)]",
5501 ""},
5502 1, curpx, &rs->rules, file, line, &errmsg);
5503 if (!chk) {
5504 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5505 goto error;
5506 }
5507 chk->index = 2;
5508 LIST_ADDQ(&rs->rules, &chk->list);
5509
5510 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5511 "min-recv", "9",
5512 "error-status", "L7STS",
5513 "on-success", "PostgreSQL server is ok",
5514 "on-error", "PostgreSQL unknown error",
5515 ""},
5516 1, curpx, &rs->rules, file, line, &errmsg);
5517 if (!chk) {
5518 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5519 goto error;
5520 }
5521 chk->index = 3;
5522 LIST_ADDQ(&rs->rules, &chk->list);
5523
5524 LIST_ADDQ(&tcpchecks_list, &rs->list);
5525
5526 ruleset_found:
5527 rules->list = &rs->rules;
5528 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK);
5529
5530 out:
5531 free(errmsg);
5532 return err_code;
5533
5534 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005535 free(user);
5536 free(var);
5537 free_tcpcheck_vars(&rules->preset_vars);
5538 tcpcheck_ruleset_release(rs);
5539 err_code |= ERR_ALERT | ERR_FATAL;
5540 goto out;
5541}
5542
5543
5544/* Parses the "option mysql-check" proxy keyword */
5545int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5546 const char *file, int line)
5547{
5548 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5549 * const char mysql40_client_auth_pkt[] = {
5550 * "\x0e\x00\x00" // packet length
5551 * "\x01" // packet number
5552 * "\x00\x00" // client capabilities
5553 * "\x00\x00\x01" // max packet
5554 * "haproxy\x00" // username (null terminated string)
5555 * "\x00" // filler (always 0x00)
5556 * "\x01\x00\x00" // packet length
5557 * "\x00" // packet number
5558 * "\x01" // COM_QUIT command
5559 * };
5560 */
5561 static char mysql40_rsname[] = "*mysql40-check";
5562 static char mysql40_req[] = {
5563 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5564 "0080" /* client capabilities */
5565 "000001" /* max packet */
5566 "%[var(check.username),hex]00" /* the username */
5567 "00" /* filler (always 0x00) */
5568 "010000" /* packet length*/
5569 "00" /* sequence ID */
5570 "01" /* COM_QUIT command */
5571 };
5572
5573 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5574 * const char mysql41_client_auth_pkt[] = {
5575 * "\x0e\x00\x00\" // packet length
5576 * "\x01" // packet number
5577 * "\x00\x00\x00\x00" // client capabilities
5578 * "\x00\x00\x00\x01" // max packet
5579 * "\x21" // character set (UTF-8)
5580 * char[23] // All zeroes
5581 * "haproxy\x00" // username (null terminated string)
5582 * "\x00" // filler (always 0x00)
5583 * "\x01\x00\x00" // packet length
5584 * "\x00" // packet number
5585 * "\x01" // COM_QUIT command
5586 * };
5587 */
5588 static char mysql41_rsname[] = "*mysql41-check";
5589 static char mysql41_req[] = {
5590 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5591 "00820000" /* client capabilities */
5592 "00800001" /* max packet */
5593 "21" /* character set (UTF-8) */
5594 "000000000000000000000000" /* 23 bytes, al zeroes */
5595 "0000000000000000000000"
5596 "%[var(check.username),hex]00" /* the username */
5597 "00" /* filler (always 0x00) */
5598 "010000" /* packet length*/
5599 "00" /* sequence ID */
5600 "01" /* COM_QUIT command */
5601 };
5602
5603 struct tcpcheck_ruleset *rs = NULL;
5604 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5605 struct tcpcheck_rule *chk;
5606 struct tcpcheck_var *var = NULL;
5607 char *mysql_rsname = "*mysql-check";
5608 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5609 int index = 0, err_code = 0;
5610
5611 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5612 err_code |= ERR_WARN;
5613
5614 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5615 goto out;
5616
5617 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5618 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5619 file, line);
5620 err_code |= ERR_ALERT | ERR_FATAL;
5621 goto out;
5622 }
5623
5624 curpx->options2 &= ~PR_O2_CHK_ANY;
5625 curpx->options2 |= PR_O2_TCPCHK_CHK;
5626
5627 free_tcpcheck_vars(&rules->preset_vars);
5628 rules->list = NULL;
5629 rules->flags = 0;
5630
5631 cur_arg += 2;
5632 if (*args[cur_arg]) {
5633 char *user;
5634 int packetlen, userlen;
5635
5636 if (strcmp(args[cur_arg], "user") != 0) {
5637 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5638 file, line, args[0], args[1], args[cur_arg]);
5639 goto error;
5640 }
5641
5642 if (*(args[cur_arg+1]) == 0) {
5643 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5644 file, line, args[0], args[1], args[cur_arg]);
5645 goto error;
5646 }
5647
5648 hdr = calloc(4, sizeof(*hdr));
5649 user = strdup(args[cur_arg+1]);
5650 userlen = strlen(args[cur_arg+1]);
5651
5652 if (hdr == NULL || user == NULL) {
5653 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5654 goto error;
5655 }
5656
5657 if (*args[cur_arg+2]) {
5658 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5659 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5660 file, line, args[cur_arg], args[cur_arg+2]);
5661 goto error;
5662 }
5663 packetlen = userlen + 7 + 27;
5664 mysql_req = mysql41_req;
5665 mysql_rsname = mysql41_rsname;
5666 }
5667 else {
5668 packetlen = userlen + 7;
5669 mysql_req = mysql40_req;
5670 mysql_rsname = mysql40_rsname;
5671 }
5672
5673 hdr[0] = (unsigned char)(packetlen & 0xff);
5674 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
5675 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
5676 hdr[3] = 1;
5677
5678 var = tcpcheck_var_create("check.header");
5679 if (var == NULL) {
5680 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5681 goto error;
5682 }
5683 var->data.type = SMP_T_STR;
5684 var->data.u.str.area = hdr;
5685 var->data.u.str.data = 4;
5686 LIST_INIT(&var->list);
5687 LIST_ADDQ(&rules->preset_vars, &var->list);
5688 hdr = NULL;
5689 var = NULL;
5690
5691 var = tcpcheck_var_create("check.username");
5692 if (var == NULL) {
5693 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5694 goto error;
5695 }
5696 var->data.type = SMP_T_STR;
5697 var->data.u.str.area = user;
5698 var->data.u.str.data = strlen(user);
5699 LIST_INIT(&var->list);
5700 LIST_ADDQ(&rules->preset_vars, &var->list);
5701 user = NULL;
5702 var = NULL;
5703 }
5704
5705 rs = tcpcheck_ruleset_lookup(mysql_rsname);
5706 if (rs)
5707 goto ruleset_found;
5708
5709 rs = tcpcheck_ruleset_create(mysql_rsname);
5710 if (rs == NULL) {
5711 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5712 goto error;
5713 }
5714
5715 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5716 1, curpx, &rs->rules, file, line, &errmsg);
5717 if (!chk) {
5718 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5719 goto error;
5720 }
5721 chk->index = index++;
5722 LIST_ADDQ(&rs->rules, &chk->list);
5723
5724 if (mysql_req) {
5725 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
5726 1, curpx, &rs->rules, file, line, &errmsg);
5727 if (!chk) {
5728 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5729 goto error;
5730 }
5731 chk->index = index++;
5732 LIST_ADDQ(&rs->rules, &chk->list);
5733 }
5734
5735 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5736 1, curpx, &rs->rules, file, line, &errmsg);
5737 if (!chk) {
5738 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5739 goto error;
5740 }
5741 chk->expect.custom = tcpcheck_mysql_expect_iniths;
5742 chk->index = index++;
5743 LIST_ADDQ(&rs->rules, &chk->list);
5744
5745 if (mysql_req) {
5746 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5747 1, curpx, &rs->rules, file, line, &errmsg);
5748 if (!chk) {
5749 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5750 goto error;
5751 }
5752 chk->expect.custom = tcpcheck_mysql_expect_ok;
5753 chk->index = index++;
5754 LIST_ADDQ(&rs->rules, &chk->list);
5755 }
5756
5757 LIST_ADDQ(&tcpchecks_list, &rs->list);
5758
5759 ruleset_found:
5760 rules->list = &rs->rules;
5761 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_MYSQL_CHK);
5762
5763 out:
5764 free(errmsg);
5765 return err_code;
5766
5767 error:
5768 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02005769 free(user);
5770 free(var);
5771 free_tcpcheck_vars(&rules->preset_vars);
5772 tcpcheck_ruleset_release(rs);
5773 err_code |= ERR_ALERT | ERR_FATAL;
5774 goto out;
5775}
5776
Christopher Faulet1997eca2020-04-03 23:13:50 +02005777int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5778 const char *file, int line)
5779{
5780 static char *ldap_req = "300C020101600702010304008000";
5781
5782 struct tcpcheck_ruleset *rs = NULL;
5783 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5784 struct tcpcheck_rule *chk;
5785 char *errmsg = NULL;
5786 int err_code = 0;
5787
5788 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5789 err_code |= ERR_WARN;
5790
5791 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5792 goto out;
5793
5794 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5795 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5796 file, line);
5797 err_code |= ERR_ALERT | ERR_FATAL;
5798 goto out;
5799 }
5800
5801 curpx->options2 &= ~PR_O2_CHK_ANY;
5802 curpx->options2 |= PR_O2_TCPCHK_CHK;
5803
5804 free_tcpcheck_vars(&rules->preset_vars);
5805 rules->list = NULL;
5806 rules->flags = 0;
5807
5808 rs = tcpcheck_ruleset_lookup("*ldap-check");
5809 if (rs)
5810 goto ruleset_found;
5811
5812 rs = tcpcheck_ruleset_create("*ldap-check");
5813 if (rs == NULL) {
5814 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5815 goto error;
5816 }
5817
5818 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
5819 1, curpx, &rs->rules, file, line, &errmsg);
5820 if (!chk) {
5821 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5822 goto error;
5823 }
5824 chk->index = 0;
5825 LIST_ADDQ(&rs->rules, &chk->list);
5826
5827 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
5828 "min-recv", "14",
5829 "on-error", "Not LDAPv3 protocol",
5830 ""},
5831 1, curpx, &rs->rules, file, line, &errmsg);
5832 if (!chk) {
5833 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5834 goto error;
5835 }
5836 chk->index = 1;
5837 LIST_ADDQ(&rs->rules, &chk->list);
5838
5839 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5840 1, curpx, &rs->rules, file, line, &errmsg);
5841 if (!chk) {
5842 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5843 goto error;
5844 }
5845 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
5846 chk->index = 2;
5847 LIST_ADDQ(&rs->rules, &chk->list);
5848
5849 LIST_ADDQ(&tcpchecks_list, &rs->list);
5850
5851 ruleset_found:
5852 rules->list = &rs->rules;
5853 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_LDAP_CHK);
5854
5855 out:
5856 free(errmsg);
5857 return err_code;
5858
5859 error:
5860 tcpcheck_ruleset_release(rs);
5861 err_code |= ERR_ALERT | ERR_FATAL;
5862 goto out;
5863}
Christopher Fauletce355072020-04-02 11:44:39 +02005864
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005865static struct cfg_kw_list cfg_kws = {ILH, {
5866 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5867 { 0, NULL, NULL },
5868}};
5869
5870INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
5871
Willy Tarreaubd741542010-03-16 18:46:54 +01005872/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02005873 * Local variables:
5874 * c-indent-level: 8
5875 * c-basic-offset: 8
5876 * End:
5877 */