blob: afa108bfd68b8768a1d8ef374af0594e6147d48b [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
Christopher Faulet799f3a42020-04-07 12:06:14 +0200638 if (check->type == PR_O2_TCPCHK_CHK &&
639 !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200640 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200641 if (!step)
642 chunk_printf(chk, " at initial connection step of tcp-check");
643 else {
644 chunk_printf(chk, " at step %d of tcp-check", step);
645 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200646 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
647 if (check->current_step->connect.port)
648 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200649 else
650 chunk_appendf(chk, " (connect)");
651 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200652 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
653 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100654
655 switch (expect->type) {
656 case TCPCHK_EXPECT_STRING:
657 chunk_appendf(chk, " (expect string '%s')", expect->string);
658 break;
659 case TCPCHK_EXPECT_BINARY:
660 chunk_appendf(chk, " (expect binary '%s')", expect->string);
661 break;
662 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200663 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100664 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100665 case TCPCHK_EXPECT_REGEX_BINARY:
666 chunk_appendf(chk, " (expect binary regex)");
667 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200668 case TCPCHK_EXPECT_CUSTOM:
669 chunk_appendf(chk, " (expect custom function)");
670 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100671 case TCPCHK_EXPECT_UNDEF:
672 chunk_appendf(chk, " (undefined expect!)");
673 break;
674 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200675 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200676 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200677 chunk_appendf(chk, " (send)");
678 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200679
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200680 if (check->current_step && check->current_step->comment)
681 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200682 }
683 }
684
Willy Tarreau00149122017-10-04 18:05:01 +0200685 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100686 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200687 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
688 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100689 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200690 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
691 chk->area);
692 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100693 }
694 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100695 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200696 chunk_printf(&trash, "%s%s", strerror(errno),
697 chk->area);
698 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100699 }
700 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200701 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100702 }
703 }
704
Willy Tarreau00149122017-10-04 18:05:01 +0200705 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200706 /* NOTE: this is reported after <fall> tries */
707 chunk_printf(chk, "No port available for the TCP connection");
708 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
709 }
710
Willy Tarreau00149122017-10-04 18:05:01 +0200711 if (!conn) {
712 /* connection allocation error before the connection was established */
713 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
714 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100715 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100716 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200717 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100718 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
719 else if (expired)
720 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200721
722 /*
723 * might be due to a server IP change.
724 * Let's trigger a DNS resolution if none are currently running.
725 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100726 if (check->server)
727 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200728
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100729 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100730 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100731 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200732 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100733 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
734 else if (expired)
735 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
736 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200737 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100738 /* I/O error after connection was established and before we could diagnose */
739 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
740 }
741 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200742 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
743
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100744 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200745 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
746 tout = check->current_step->expect.tout_status;
747 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100748 }
749
750 return;
751}
752
Olivier Houchard5c110b92018-08-14 17:04:58 +0200753/* This function checks if any I/O is wanted, and if so, attempts to do so */
754static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200755{
Olivier Houchard26e1a8f2018-09-12 15:15:12 +0200756 struct check *check = ctx;
757 struct conn_stream *cs = check->cs;
Olivier Houchard0923fa42019-01-11 18:43:04 +0100758 struct email_alertq *q = container_of(check, typeof(*q), check);
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200759 int ret = 0;
Olivier Houchard4501c3e2018-08-28 19:36:18 +0200760
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100761 if (!(check->wait_list.events & SUB_RETRY_SEND))
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200762 ret = wake_srv_chk(cs);
763 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
Olivier Houchard0923fa42019-01-11 18:43:04 +0100764 if (check->server)
765 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
766 else
767 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200768 __event_srv_chk_r(cs);
Olivier Houchard0923fa42019-01-11 18:43:04 +0100769 if (check->server)
770 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
771 else
772 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200773 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200774 return NULL;
775}
776
777/* same as above but protected by the server lock.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100778 *
779 * Please do NOT place any return statement in this function and only leave
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200780 * via the out label. NOTE THAT THIS FUNCTION DOESN'T LOCK, YOU PROBABLY WANT
781 * TO USE event_srv_chk_w() instead.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200782 */
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200783static void __event_srv_chk_w(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200784{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200785 struct connection *conn = cs->conn;
786 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900787 struct server *s = check->server;
Simon Horman4a741432013-02-23 15:35:38 +0900788 struct task *t = check->task;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200789
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100790 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100791 goto out_wakeup;
792
Willy Tarreau20a18342013-12-05 00:31:46 +0100793 if (retrieve_errno_from_socket(conn)) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200794 chk_report_conn_err(check, errno, 0);
Willy Tarreau20a18342013-12-05 00:31:46 +0100795 goto out_wakeup;
796 }
Krzysztof Piotr Oledzki6492db52010-01-02 22:03:01 +0100797
Willy Tarreau06559ac2013-12-05 01:53:08 +0100798 /* here, we know that the connection is established. That's enough for
799 * a pure TCP check.
800 */
801 if (!check->type)
802 goto out_wakeup;
803
Willy Tarreauc09572f2017-10-04 11:58:22 +0200804 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100805 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200806 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200807
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200808 if (b_data(&check->bo)) {
Olivier Houcharded0f2072018-08-16 15:41:52 +0200809 cs->conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200810 b_realign_if_empty(&check->bo);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200811 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200812 chk_report_conn_err(check, errno, 0);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100813 goto out_wakeup;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200814 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200815 if (b_data(&check->bo)) {
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100816 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200817 goto out;
818 }
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100819 }
Willy Tarreau6996e152007-04-30 14:37:43 +0200820
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100821 /* full request sent, we allow up to <timeout.check> if nonzero for a response */
822 if (s->proxy->timeout.check) {
823 t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
824 task_queue(t);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200825 }
Olivier Houchard53216e72018-10-10 15:46:36 +0200826 goto out;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100827
Willy Tarreau83749182007-04-15 20:56:27 +0200828 out_wakeup:
Willy Tarreaufdccded2008-08-29 18:19:04 +0200829 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200830 out:
831 return;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200832}
833
Willy Tarreaubaaee002006-06-26 02:48:02 +0200834/*
Willy Tarreauf3c69202006-07-09 16:42:34 +0200835 * This function is used only for server health-checks. It handles the server's
Hervé COMMOWICK8776f1b2010-10-18 15:58:36 +0200836 * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
Simon Horman4a741432013-02-23 15:35:38 +0900837 * set_server_check_status() to update check->status, check->duration
838 * and check->result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200839
840 * The set_server_check_status function is called with HCHK_STATUS_L7OKD if
841 * an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
842 * returns 2xx, HCHK_STATUS_L6OK if an SSL server returns at least 5 bytes in
843 * response to an SSL HELLO (the principle is that this is enough to
844 * distinguish between an SSL server and a pure TCP relay). All other cases will
845 * call it with a proper error status like HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP,
846 * etc.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100847 *
848 * Please do NOT place any return statement in this function and only leave
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200849 * via the out label.
850 *
851 * This must be called with the server lock held.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200852 */
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200853static void __event_srv_chk_r(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200854{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200855 struct connection *conn = cs->conn;
856 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900857 struct server *s = check->server;
858 struct task *t = check->task;
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200859 char *desc;
Willy Tarreau03938182010-03-17 21:52:07 +0100860 int done;
Willy Tarreau83749182007-04-15 20:56:27 +0200861
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100862 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau83749182007-04-15 20:56:27 +0200863 goto out_wakeup;
Willy Tarreau83749182007-04-15 20:56:27 +0200864
Willy Tarreauc09572f2017-10-04 11:58:22 +0200865 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100866 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200867 goto out;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200868
Willy Tarreau83749182007-04-15 20:56:27 +0200869 /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available
870 * but the connection was closed on the remote end. Fortunately, recv still
871 * works correctly and we don't need to do the getsockopt() on linux.
872 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000873
874 /* Set buffer to point to the end of the data already read, and check
875 * that there is free space remaining. If the buffer is full, proceed
876 * with running the checks without attempting another socket read.
877 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000878
Willy Tarreau03938182010-03-17 21:52:07 +0100879 done = 0;
Nick Chalk57b1bf72010-03-16 15:50:46 +0000880
Olivier Houchard511efea2018-08-16 15:30:32 +0200881 cs->conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200882 if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
Willy Tarreau03938182010-03-17 21:52:07 +0100883 done = 1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200884 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
Willy Tarreauf1503172012-09-28 19:39:36 +0200885 /* Report network errors only if we got no other data. Otherwise
886 * we'll let the upper layers decide whether the response is OK
887 * or not. It is very common that an RST sent by the server is
888 * reported as an error just after the last data chunk.
889 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200890 chk_report_conn_err(check, errno, 0);
Willy Tarreauc1a07962010-03-16 20:55:43 +0100891 goto out_wakeup;
892 }
Willy Tarreaubaaee002006-06-26 02:48:02 +0200893 }
894
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200895 /* the rest of the code below expects the connection to be ready! */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100896 if (conn->flags & CO_FL_WAIT_XPRT && !done)
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200897 goto wait_more_data;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100898
Willy Tarreau03938182010-03-17 21:52:07 +0100899 /* Intermediate or complete response received.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200900 * Terminate string in b_head(&check->bi) buffer.
Willy Tarreau03938182010-03-17 21:52:07 +0100901 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200902 if (b_data(&check->bi) < b_size(&check->bi))
903 b_head(&check->bi)[b_data(&check->bi)] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100904 else {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200905 b_head(&check->bi)[b_data(&check->bi) - 1] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100906 done = 1; /* buffer full, don't wait for more data */
907 }
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200908
Nick Chalk57b1bf72010-03-16 15:50:46 +0000909 /* Run the checks... */
Simon Horman4a741432013-02-23 15:35:38 +0900910 switch (check->type) {
Willy Tarreau1620ec32011-08-06 17:05:02 +0200911 case PR_O2_HTTP_CHK:
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200912 if (!done && b_data(&check->bi) < strlen("HTTP/1.0 000\r"))
Willy Tarreau03938182010-03-17 21:52:07 +0100913 goto wait_more_data;
914
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100915 /* Check if the server speaks HTTP 1.X */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200916 if ((b_data(&check->bi) < strlen("HTTP/1.0 000\r")) ||
917 (memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
918 (*(b_head(&check->bi) + 12) != ' ' && *(b_head(&check->bi) + 12) != '\r')) ||
919 !isdigit((unsigned char) *(b_head(&check->bi) + 9)) || !isdigit((unsigned char) *(b_head(&check->bi) + 10)) ||
920 !isdigit((unsigned char) *(b_head(&check->bi) + 11))) {
921 cut_crlf(b_head(&check->bi));
922 set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200923
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100924 goto out_wakeup;
925 }
926
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200927 check->code = str2uic(b_head(&check->bi) + 9);
928 desc = ltrim(b_head(&check->bi) + 12, ' ');
Christopher Fauletc2a89a62017-10-23 15:54:24 +0200929
Willy Tarreaubd741542010-03-16 18:46:54 +0100930 if ((s->proxy->options & PR_O_DISABLE404) &&
Emeric Brun52a91d32017-08-31 14:41:55 +0200931 (s->next_state != SRV_ST_STOPPED) && (check->code == 404)) {
Nick Chalk57b1bf72010-03-16 15:50:46 +0000932 /* 404 may be accepted as "stopping" only if the server was up */
933 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900934 set_server_check_status(check, HCHK_STATUS_L7OKCD, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000935 }
Willy Tarreaubd741542010-03-16 18:46:54 +0100936 else if (s->proxy->options2 & PR_O2_EXP_TYPE) {
937 /* Run content verification check... We know we have at least 13 chars */
938 if (!httpchk_expect(s, done))
939 goto wait_more_data;
940 }
941 /* check the reply : HTTP/1.X 2xx and 3xx are OK */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200942 else if (*(b_head(&check->bi) + 9) == '2' || *(b_head(&check->bi) + 9) == '3') {
Willy Tarreaubd741542010-03-16 18:46:54 +0100943 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900944 set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
Willy Tarreaubd741542010-03-16 18:46:54 +0100945 }
Nick Chalk57b1bf72010-03-16 15:50:46 +0000946 else {
947 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900948 set_server_check_status(check, HCHK_STATUS_L7STS, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000949 }
Willy Tarreau1620ec32011-08-06 17:05:02 +0200950 break;
951
Willy Tarreau1620ec32011-08-06 17:05:02 +0200952 default:
Willy Tarreau4c1a2b32019-09-05 18:43:22 +0200953 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100954 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +0100955 if (check->use_ssl == 1)
Willy Tarreau4c1a2b32019-09-05 18:43:22 +0200956 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
957 else
958 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
959 }
Willy Tarreau1620ec32011-08-06 17:05:02 +0200960 break;
961 } /* switch */
Willy Tarreau83749182007-04-15 20:56:27 +0200962
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100963 out_wakeup:
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100964 /* collect possible new errors */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200965 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200966 chk_report_conn_err(check, 0, 0);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200967
Nick Chalk57b1bf72010-03-16 15:50:46 +0000968 /* Reset the check buffer... */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200969 *b_head(&check->bi) = '\0';
970 b_reset(&check->bi);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000971
Steven Davidovitz544d4812017-03-08 11:06:20 -0800972 /* Close the connection... We still attempt to nicely close if,
973 * for instance, SSL needs to send a "close notify." Later, we perform
974 * a hard close and reset the connection if some data are pending,
975 * otherwise we end up with many TIME_WAITs and eat all the source port
976 * range quickly. To avoid sending RSTs all the time, we first try to
977 * drain pending data.
Willy Tarreaufd29cc52012-11-23 09:18:20 +0100978 */
Olivier Houchard6c7e96a2019-07-02 16:35:18 +0200979 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
980 * connection, to make sure cs_shutw() will not lead to a shutdown()
981 * that would provoke TIME_WAITs.
982 */
983 cs_shutr(cs, CS_SHR_DRAIN);
Willy Tarreauecdb3fe2017-10-05 15:25:48 +0200984 cs_shutw(cs, CS_SHW_NORMAL);
Willy Tarreau2b57cb82013-06-10 19:56:38 +0200985
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100986 /* OK, let's not stay here forever */
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100987 if (check->result == CHK_RES_FAILED)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100988 conn->flags |= CO_FL_ERROR;
989
Willy Tarreaufdccded2008-08-29 18:19:04 +0200990 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200991out:
Willy Tarreau3267d362012-08-17 23:53:56 +0200992 return;
Willy Tarreau03938182010-03-17 21:52:07 +0100993
994 wait_more_data:
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100995 cs->conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200996 goto out;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200997}
998
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200999/*
1000 * This function is used only for server health-checks. It handles connection
1001 * status updates including errors. If necessary, it wakes the check task up.
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001002 * It returns 0 on normal cases, <0 if at least one close() has happened on the
1003 * connection (eg: reconnect).
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001004 */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001005static int wake_srv_chk(struct conn_stream *cs)
Willy Tarreau20bea422012-07-06 12:00:49 +02001006{
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001007 struct connection *conn = cs->conn;
1008 struct check *check = cs->data;
Olivier Houchard0923fa42019-01-11 18:43:04 +01001009 struct email_alertq *q = container_of(check, typeof(*q), check);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001010 int ret = 0;
Willy Tarreau20bea422012-07-06 12:00:49 +02001011
Olivier Houchard0923fa42019-01-11 18:43:04 +01001012 if (check->server)
1013 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
1014 else
1015 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001016
Willy Tarreauc09572f2017-10-04 11:58:22 +02001017 /* we may have to make progress on the TCP checks */
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001018 if (check->type == PR_O2_TCPCHK_CHK) {
1019 ret = tcpcheck_main(check);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001020 cs = check->cs;
Willy Tarreau543abd42018-09-20 11:25:12 +02001021 conn = cs->conn;
Willy Tarreauc5940392019-09-05 17:38:40 +02001022 } else {
1023 if (!(check->wait_list.events & SUB_RETRY_SEND))
1024 __event_srv_chk_w(cs);
1025 if (!(check->wait_list.events & SUB_RETRY_RECV))
1026 __event_srv_chk_r(cs);
1027 }
Willy Tarreauc09572f2017-10-04 11:58:22 +02001028
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001029 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
Willy Tarreau02b0f582013-12-03 15:42:33 +01001030 /* We may get error reports bypassing the I/O handlers, typically
1031 * the case when sending a pure TCP check which fails, then the I/O
1032 * handlers above are not called. This is completely handled by the
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001033 * main processing task so let's simply wake it up. If we get here,
1034 * we expect errno to still be valid.
1035 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001036 chk_report_conn_err(check, errno, 0);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001037 task_wakeup(check->task, TASK_WOKEN_IO);
1038 }
Willy Tarreau911db9b2020-01-23 16:27:54 +01001039 else if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Willy Tarreau3be293f2014-02-05 18:31:24 +01001040 /* we may get here if only a connection probe was required : we
1041 * don't have any data to send nor anything expected in response,
1042 * so the completion of the connection establishment is enough.
1043 */
1044 task_wakeup(check->task, TASK_WOKEN_IO);
1045 }
Willy Tarreau2d351b62013-12-05 02:36:25 +01001046
Willy Tarreau6aaa1b82013-12-11 17:09:34 +01001047 if (check->result != CHK_RES_UNKNOWN) {
Christopher Faulet774c4862019-01-21 14:15:50 +01001048 /* Check complete or aborted. If connection not yet closed do it
1049 * now and wake the check task up to be sure the result is
1050 * handled ASAP. */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001051 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001052 cs_close(cs);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001053 ret = -1;
Olivier Houchardf4949572019-07-02 17:42:22 +02001054 /* We may have been scheduled to run, and the
1055 * I/O handler expects to have a cs, so remove
1056 * the tasklet
1057 */
1058 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Christopher Faulet774c4862019-01-21 14:15:50 +01001059 task_wakeup(check->task, TASK_WOKEN_IO);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001060 }
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001061
Olivier Houchard0923fa42019-01-11 18:43:04 +01001062 if (check->server)
1063 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
1064 else
1065 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001066
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001067 /* if a connection got replaced, we must absolutely prevent the connection
1068 * handler from touching its fd, and perform the FD polling updates ourselves
1069 */
1070 if (ret < 0)
1071 conn_cond_update_polling(conn);
1072
1073 return ret;
Willy Tarreau20bea422012-07-06 12:00:49 +02001074}
1075
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001076struct data_cb check_conn_cb = {
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001077 .wake = wake_srv_chk,
Willy Tarreau8e0bb0a2016-11-24 16:58:12 +01001078 .name = "CHCK",
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001079};
1080
Willy Tarreaubaaee002006-06-26 02:48:02 +02001081/*
Willy Tarreau2e993902011-10-31 11:53:20 +01001082 * updates the server's weight during a warmup stage. Once the final weight is
1083 * reached, the task automatically stops. Note that any server status change
1084 * must have updated s->last_change accordingly.
1085 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001086static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Willy Tarreau2e993902011-10-31 11:53:20 +01001087{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001088 struct server *s = context;
Willy Tarreau2e993902011-10-31 11:53:20 +01001089
1090 /* by default, plan on stopping the task */
1091 t->expire = TICK_ETERNITY;
Emeric Brun52a91d32017-08-31 14:41:55 +02001092 if ((s->next_admin & SRV_ADMF_MAINT) ||
1093 (s->next_state != SRV_ST_STARTING))
Willy Tarreau2e993902011-10-31 11:53:20 +01001094 return t;
1095
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001096 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
1097
Willy Tarreau892337c2014-05-13 23:41:20 +02001098 /* recalculate the weights and update the state */
Willy Tarreau3ff577e2018-08-02 11:48:52 +02001099 server_recalc_eweight(s, 1);
Willy Tarreau2e993902011-10-31 11:53:20 +01001100
1101 /* probably that we can refill this server with a bit more connections */
Willy Tarreau4aac7db2014-05-16 11:48:10 +02001102 pendconn_grab_from_px(s);
Willy Tarreau2e993902011-10-31 11:53:20 +01001103
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001104 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
1105
Willy Tarreau2e993902011-10-31 11:53:20 +01001106 /* get back there in 1 second or 1/20th of the slowstart interval,
1107 * whichever is greater, resulting in small 5% steps.
1108 */
Emeric Brun52a91d32017-08-31 14:41:55 +02001109 if (s->next_state == SRV_ST_STARTING)
Willy Tarreau2e993902011-10-31 11:53:20 +01001110 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
1111 return t;
1112}
1113
Willy Tarreau894c6422017-10-04 15:58:52 +02001114/* returns the first NON-COMMENT tcp-check rule from list <list> or NULL if
1115 * none was found.
1116 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001117static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Willy Tarreau894c6422017-10-04 15:58:52 +02001118{
1119 struct tcpcheck_rule *r;
1120
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001121 list_for_each_entry(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001122 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Willy Tarreau894c6422017-10-04 15:58:52 +02001123 return r;
1124 }
1125 return NULL;
1126}
1127
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001128/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1129 * NULL if non was found. If <start> is NULL, it relies on
1130 * get_first_tcpcheck_rule().
1131 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001132static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001133{
1134 struct tcpcheck_rule *r;
1135
1136 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001137 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001138
1139 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001140 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001141 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001142 return r;
1143 }
1144 return NULL;
1145}
1146
Willy Tarreau2e993902011-10-31 11:53:20 +01001147/*
Simon Horman98637e52014-06-20 12:30:16 +09001148 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001149 *
1150 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001151 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1152 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1153 * - SF_ERR_SRVTO if there are no more servers
1154 * - SF_ERR_SRVCL if the connection was refused by the server
1155 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1156 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1157 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001158 * - 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 +01001159 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001160 * Note that we try to prevent the network stack from sending the ACK during the
1161 * connect() when a pure TCP check is used (without PROXY protocol).
1162 */
Simon Horman98637e52014-06-20 12:30:16 +09001163static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001164{
1165 struct check *check = t->context;
1166 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001167 struct conn_stream *cs = check->cs;
1168 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001169 struct protocol *proto;
1170 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001171 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001172
Willy Tarreau00149122017-10-04 18:05:01 +02001173 /* we cannot have a connection here */
1174 if (conn)
1175 return SF_ERR_INTERNAL;
1176
Simon Hormanb00d17a2014-06-13 16:18:16 +09001177 /* prepare the check buffer.
1178 * This should not be used if check is the secondary agent check
1179 * of a server as s->proxy->check_req will relate to the
1180 * configuration of the primary check. Similarly, tcp-check uses
1181 * its own strings.
1182 */
1183 if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001184 b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001185
Christopher Faulet811f78c2020-04-01 11:10:27 +02001186 /* we want to check if this host replies to HTTP requests
Simon Hormanb00d17a2014-06-13 16:18:16 +09001187 * so we'll send the request, and won't wake the checker up now.
1188 */
Christopher Faulet811f78c2020-04-01 11:10:27 +02001189 if ((check->type) == PR_O2_HTTP_CHK) {
Cyril Bonté32602d22015-01-30 00:07:07 +01001190 /* prevent HTTP keep-alive when "http-check expect" is used */
1191 if (s->proxy->options2 & PR_O2_EXP_TYPE)
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001192 b_putist(&check->bo, ist("Connection: close\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001193
1194 /* If there is a body, add its content-length */
1195 if (s->proxy->check_body_len)
1196 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
1197
1198 /* Add configured headers */
1199 if (s->proxy->check_hdrs)
1200 b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
1201
1202 /* Add send-state header */
1203 if (s->proxy->options2 & PR_O2_CHK_SNDST)
1204 b_putblk(&check->bo, trash.area,
1205 httpchk_build_status_header(s, trash.area, trash.size));
1206
1207 /* end-of-header */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001208 b_putist(&check->bo, ist("\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001209
1210 /* Add the body */
1211 if (s->proxy->check_body)
1212 b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
1213
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001214 *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
Simon Hormanb00d17a2014-06-13 16:18:16 +09001215 }
1216 }
1217
Willy Tarreauf411cce2017-10-04 16:21:19 +02001218 /* for tcp-checks, the initial connection setup is handled separately as
1219 * it may be sent to a specific port and not to the server's.
1220 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001221 if (check->type == PR_O2_TCPCHK_CHK) {
1222 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001223 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001224 tcpcheck_main(check);
1225 return SF_ERR_UP;
1226 }
1227
Simon Hormanb00d17a2014-06-13 16:18:16 +09001228 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001229 cs = check->cs = cs_new(NULL);
1230 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001231 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001232 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001233 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001234 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001235 tasklet_set_tid(check->wait_list.tasklet, tid);
1236
Simon Hormanb00d17a2014-06-13 16:18:16 +09001237
Willy Tarreauca79f592019-07-17 19:04:47 +02001238 if (!sockaddr_alloc(&conn->dst))
1239 return SF_ERR_RESOURCE;
1240
Simon Horman41f58762015-01-30 11:22:56 +09001241 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001242 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001243 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001244 }
1245 else {
1246 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001247 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001248 }
1249
Alexander Liu2a54bb72019-05-22 19:44:48 +08001250 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1251 conn->send_proxy_ofs = 1;
1252 conn->flags |= CO_FL_SOCKS4;
1253 }
1254
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001255 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001256 conn->target = &s->obj_type;
1257
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001258 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001259 int i = 0;
1260
1261 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001262 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001263 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001264
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001265 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001266 }
1267
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001268 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001269
Willy Tarreaube373152018-09-06 11:45:30 +02001270 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001271 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1272 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001273 cs_attach(cs, check, &check_conn_cb);
1274
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001275 /* only plain tcp check supports quick ACK */
1276 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001277
Willy Tarreaue7dff022015-04-03 01:14:29 +02001278 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001279 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001280 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001281
Willy Tarreau16257f62017-11-02 15:45:00 +01001282
Olivier Houchard9130a962017-10-17 17:33:43 +02001283#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001284 if (ret == SF_ERR_NONE) {
1285 if (s->check.sni)
1286 ssl_sock_set_servername(conn, s->check.sni);
1287 if (s->check.alpn_str)
1288 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1289 s->check.alpn_len);
1290 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001291#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001292 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001293 conn->send_proxy_ofs = 1;
1294 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001295 }
1296 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1297 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001298 if (xprt_add_hs(conn) < 0)
1299 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001300 }
1301
1302 return ret;
1303}
1304
Simon Horman98637e52014-06-20 12:30:16 +09001305static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001306static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001307__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001308
1309void block_sigchld(void)
1310{
1311 sigset_t set;
1312 sigemptyset(&set);
1313 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001314 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001315}
1316
1317void unblock_sigchld(void)
1318{
1319 sigset_t set;
1320 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001321 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001322 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001323}
1324
Simon Horman98637e52014-06-20 12:30:16 +09001325static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1326{
1327 struct pid_list *elem;
1328 struct check *check = t->context;
1329
Willy Tarreaubafbe012017-11-24 17:34:44 +01001330 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001331 if (!elem)
1332 return NULL;
1333 elem->pid = pid;
1334 elem->t = t;
1335 elem->exited = 0;
1336 check->curpid = elem;
1337 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001338
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001339 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001340 LIST_ADD(&pid_list, &elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001341 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001342
Simon Horman98637e52014-06-20 12:30:16 +09001343 return elem;
1344}
1345
Simon Horman98637e52014-06-20 12:30:16 +09001346static void pid_list_del(struct pid_list *elem)
1347{
1348 struct check *check;
1349
1350 if (!elem)
1351 return;
1352
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001353 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001354 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001355 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001356
Simon Horman98637e52014-06-20 12:30:16 +09001357 if (!elem->exited)
1358 kill(elem->pid, SIGTERM);
1359
1360 check = elem->t->context;
1361 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001362 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001363}
1364
1365/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1366static void pid_list_expire(pid_t pid, int status)
1367{
1368 struct pid_list *elem;
1369
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001370 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001371 list_for_each_entry(elem, &pid_list, list) {
1372 if (elem->pid == pid) {
1373 elem->t->expire = now_ms;
1374 elem->status = status;
1375 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001376 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001377 break;
Simon Horman98637e52014-06-20 12:30:16 +09001378 }
1379 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001380 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001381}
1382
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001383static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001384{
1385 pid_t pid;
1386 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001387
Simon Horman98637e52014-06-20 12:30:16 +09001388 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1389 pid_list_expire(pid, status);
1390}
1391
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001392static int init_pid_list(void)
1393{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001394 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001395 /* Nothing to do */
1396 return 0;
1397
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001398 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001399 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1400 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001401 return 1;
1402 }
1403
Willy Tarreaubafbe012017-11-24 17:34:44 +01001404 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1405 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001406 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1407 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001408 return 1;
1409 }
1410
1411 return 0;
1412}
1413
Cyril Bontéac92a062014-12-27 22:28:38 +01001414/* helper macro to set an environment variable and jump to a specific label on failure. */
1415#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001416
1417/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001418 * helper function to allocate enough memory to store an environment variable.
1419 * It will also check that the environment variable is updatable, and silently
1420 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001421 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001422static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001423{
1424 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001425 char *envname;
1426 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001427
Cyril Bontéac92a062014-12-27 22:28:38 +01001428 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001429 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001430 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001431 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001432
1433 envname = extcheck_envs[idx].name;
1434 vmaxlen = extcheck_envs[idx].vmaxlen;
1435
1436 /* Check if the environment variable is already set, and silently reject
1437 * the update if this one is not updatable. */
1438 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1439 return 0;
1440
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001441 /* Instead of sending NOT_USED, sending an empty value is preferable */
1442 if (strcmp(value, "NOT_USED") == 0) {
1443 value = "";
1444 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001445
1446 len = strlen(envname) + 1;
1447 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1448 len += strlen(value);
1449 else
1450 len += vmaxlen;
1451
1452 if (!check->envp[idx])
1453 check->envp[idx] = malloc(len + 1);
1454
1455 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001456 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001457 return 1;
1458 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001459 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001460 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001461 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001462 return 1;
1463 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001464 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001465 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001466 return 1;
1467 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001468 return 0;
1469}
Simon Horman98637e52014-06-20 12:30:16 +09001470
1471static int prepare_external_check(struct check *check)
1472{
1473 struct server *s = check->server;
1474 struct proxy *px = s->proxy;
1475 struct listener *listener = NULL, *l;
1476 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001477 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001478 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001479
1480 list_for_each_entry(l, &px->conf.listeners, by_fe)
1481 /* Use the first INET, INET6 or UNIX listener */
1482 if (l->addr.ss_family == AF_INET ||
1483 l->addr.ss_family == AF_INET6 ||
1484 l->addr.ss_family == AF_UNIX) {
1485 listener = l;
1486 break;
1487 }
1488
Simon Horman98637e52014-06-20 12:30:16 +09001489 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001490 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1491 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001492 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001493 goto err;
1494 }
Simon Horman98637e52014-06-20 12:30:16 +09001495
Cyril Bontéac92a062014-12-27 22:28:38 +01001496 check->argv = calloc(6, sizeof(char *));
1497 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001498 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001499 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001500 }
Simon Horman98637e52014-06-20 12:30:16 +09001501
1502 check->argv[0] = px->check_command;
1503
Cyril Bonté777be862014-12-02 21:21:35 +01001504 if (!listener) {
1505 check->argv[1] = strdup("NOT_USED");
1506 check->argv[2] = strdup("NOT_USED");
1507 }
1508 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001509 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001510 addr_to_str(&listener->addr, buf, sizeof(buf));
1511 check->argv[1] = strdup(buf);
1512 port_to_str(&listener->addr, buf, sizeof(buf));
1513 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001514 }
1515 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001516 const struct sockaddr_un *un;
1517
1518 un = (struct sockaddr_un *)&listener->addr;
1519 check->argv[1] = strdup(un->sun_path);
1520 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001521 }
1522 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001523 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001524 goto err;
1525 }
1526
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001527 if (!check->argv[1] || !check->argv[2]) {
1528 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1529 goto err;
1530 }
1531
1532 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1533 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1534 if (!check->argv[3] || !check->argv[4]) {
1535 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1536 goto err;
1537 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001538
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001539 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001540 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001541 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001542
Cyril Bontéac92a062014-12-27 22:28:38 +01001543 for (i = 0; i < 5; i++) {
1544 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001545 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001546 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001547 }
1548 }
Simon Horman98637e52014-06-20 12:30:16 +09001549
Cyril Bontéac92a062014-12-27 22:28:38 +01001550 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001551 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001552 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1553 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1554 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1555 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001556 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001557 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1558 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1559 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1560 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1561 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1562 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1563
1564 /* Ensure that we don't leave any hole in check->envp */
1565 for (i = 0; i < EXTCHK_SIZE; i++)
1566 if (!check->envp[i])
1567 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001568
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001569 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001570err:
1571 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001572 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001573 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001574 free(check->envp);
1575 check->envp = NULL;
1576 }
1577
1578 if (check->argv) {
1579 for (i = 1; i < 5; i++)
1580 free(check->argv[i]);
1581 free(check->argv);
1582 check->argv = NULL;
1583 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001584 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001585}
1586
Simon Hormanb00d17a2014-06-13 16:18:16 +09001587/*
Simon Horman98637e52014-06-20 12:30:16 +09001588 * establish a server health-check that makes use of a process.
1589 *
1590 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001591 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001592 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001593 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001594 *
1595 * Blocks and then unblocks SIGCHLD
1596 */
1597static int connect_proc_chk(struct task *t)
1598{
Cyril Bontéac92a062014-12-27 22:28:38 +01001599 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001600 struct check *check = t->context;
1601 struct server *s = check->server;
1602 struct proxy *px = s->proxy;
1603 int status;
1604 pid_t pid;
1605
Willy Tarreaue7dff022015-04-03 01:14:29 +02001606 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001607
1608 block_sigchld();
1609
1610 pid = fork();
1611 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001612 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1613 (global.tune.options & GTUNE_INSECURE_FORK) ?
1614 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001615 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001616 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1617 goto out;
1618 }
1619 if (pid == 0) {
1620 /* Child */
1621 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001622 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001623 int fd;
1624
1625 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1626 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1627
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001628 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001629
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001630 /* restore the initial FD limits */
1631 limit.rlim_cur = rlim_fd_cur_at_boot;
1632 limit.rlim_max = rlim_fd_max_at_boot;
1633 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1634 getrlimit(RLIMIT_NOFILE, &limit);
1635 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1636 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1637 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1638 }
1639
Simon Horman98637e52014-06-20 12:30:16 +09001640 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001641
1642 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001643 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001644
1645 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1646 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1647
1648 *check->argv[4] = 0;
1649 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1650 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1651 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1652
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001653 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001654 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001655 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1656 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001657 exit(-1);
1658 }
1659
1660 /* Parent */
1661 if (check->result == CHK_RES_UNKNOWN) {
1662 if (pid_list_add(pid, t) != NULL) {
1663 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1664
1665 if (px->timeout.check && px->timeout.connect) {
1666 int t_con = tick_add(now_ms, px->timeout.connect);
1667 t->expire = tick_first(t->expire, t_con);
1668 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001669 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001670 goto out;
1671 }
1672 else {
1673 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1674 }
1675 kill(pid, SIGTERM); /* process creation error */
1676 }
1677 else
1678 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1679
1680out:
1681 unblock_sigchld();
1682 return status;
1683}
1684
1685/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001686 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001687 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001688 *
1689 * Please do NOT place any return statement in this function and only leave
1690 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001691 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001692static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001693{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001694 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001695 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001696 int rv;
1697 int ret;
1698 int expired = tick_is_expired(t->expire, now_ms);
1699
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001700 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001701 if (!(check->state & CHK_ST_INPROGRESS)) {
1702 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001703 if (!expired) /* woke up too early */
1704 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001705
1706 /* we don't send any health-checks when the proxy is
1707 * stopped, the server should not be checked or the check
1708 * is disabled.
1709 */
1710 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1711 s->proxy->state == PR_STSTOPPED)
1712 goto reschedule;
1713
1714 /* we'll initiate a new check */
1715 set_server_check_status(check, HCHK_STATUS_START, NULL);
1716
1717 check->state |= CHK_ST_INPROGRESS;
1718
Simon Hormandbf70192015-01-30 11:22:53 +09001719 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001720 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001721 /* the process was forked, we allow up to min(inter,
1722 * timeout.connect) for it to report its status, but
1723 * only when timeout.check is set as it may be to short
1724 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001725 */
1726 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1727
1728 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1729 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1730 t->expire = tick_first(t->expire, t_con);
1731 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001732 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001733 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001734 }
1735
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001736 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001737
1738 check->state &= ~CHK_ST_INPROGRESS;
1739 check_notify_failure(check);
1740
1741 /* we allow up to min(inter, timeout.connect) for a connection
1742 * to establish but only when timeout.check is set
1743 * as it may be to short for a full check otherwise
1744 */
1745 while (tick_is_expired(t->expire, now_ms)) {
1746 int t_con;
1747
1748 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1749 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1750
1751 if (s->proxy->timeout.check)
1752 t->expire = tick_first(t->expire, t_con);
1753 }
1754 }
1755 else {
1756 /* there was a test running.
1757 * First, let's check whether there was an uncaught error,
1758 * which can happen on connect timeout or error.
1759 */
1760 if (check->result == CHK_RES_UNKNOWN) {
1761 /* good connection is enough for pure TCP check */
1762 struct pid_list *elem = check->curpid;
1763 int status = HCHK_STATUS_UNKNOWN;
1764
1765 if (elem->exited) {
1766 status = elem->status; /* Save in case the process exits between use below */
1767 if (!WIFEXITED(status))
1768 check->code = -1;
1769 else
1770 check->code = WEXITSTATUS(status);
1771 if (!WIFEXITED(status) || WEXITSTATUS(status))
1772 status = HCHK_STATUS_PROCERR;
1773 else
1774 status = HCHK_STATUS_PROCOK;
1775 } else if (expired) {
1776 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01001777 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09001778 kill(elem->pid, SIGTERM);
1779 }
1780 set_server_check_status(check, status, NULL);
1781 }
1782
1783 if (check->result == CHK_RES_FAILED) {
1784 /* a failure or timeout detected */
1785 check_notify_failure(check);
1786 }
1787 else if (check->result == CHK_RES_CONDPASS) {
1788 /* check is OK but asks for stopping mode */
1789 check_notify_stopping(check);
1790 }
1791 else if (check->result == CHK_RES_PASSED) {
1792 /* a success was detected */
1793 check_notify_success(check);
1794 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001795 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09001796 check->state &= ~CHK_ST_INPROGRESS;
1797
1798 pid_list_del(check->curpid);
1799
1800 rv = 0;
1801 if (global.spread_checks > 0) {
1802 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001803 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09001804 }
1805 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
1806 }
1807
1808 reschedule:
1809 while (tick_is_expired(t->expire, now_ms))
1810 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001811
1812 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001813 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001814 return t;
1815}
1816
1817/*
1818 * manages a server health-check that uses a connection. Returns
1819 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001820 *
1821 * Please do NOT place any return statement in this function and only leave
1822 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09001823 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001824static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001825{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001826 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001827 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001828 struct conn_stream *cs = check->cs;
1829 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001830 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001831 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001832 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001833
Olivier Houchard0923fa42019-01-11 18:43:04 +01001834 if (check->server)
1835 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001836 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01001837 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001838 if (!expired) /* woke up too early */
1839 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001840
Simon Horman671b6f02013-11-25 10:46:39 +09001841 /* we don't send any health-checks when the proxy is
1842 * stopped, the server should not be checked or the check
1843 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001844 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01001845 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001846 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01001847 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001848
1849 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09001850 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001851
Willy Tarreau2c115e52013-12-11 19:41:16 +01001852 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001853 b_reset(&check->bi);
1854 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001855
Olivier Houchardaebeff72019-11-29 16:18:51 +01001856 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09001857 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001858 cs = check->cs;
1859 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02001860
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001861 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02001862 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001863 goto out_unlock;
1864
Willy Tarreaue7dff022015-04-03 01:14:29 +02001865 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001866 /* we allow up to min(inter, timeout.connect) for a connection
1867 * to establish but only when timeout.check is set
1868 * as it may be to short for a full check otherwise
1869 */
Simon Horman4a741432013-02-23 15:35:38 +09001870 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001871 if (proxy->timeout.check && proxy->timeout.connect) {
1872 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001873 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001874 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001875
Willy Tarreaucc705a62019-09-05 17:51:30 +02001876 if (check->type) {
1877 /* send the request if we have one. We avoid receiving
1878 * if not connected, unless we didn't subscribe for
1879 * sending since otherwise we won't be woken up.
1880 */
1881 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01001882 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02001883 !(check->wait_list.events & SUB_RETRY_SEND))
1884 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02001885 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001886
Willy Tarreau5a78f362012-11-23 12:47:05 +01001887 goto reschedule;
1888
Willy Tarreaue7dff022015-04-03 01:14:29 +02001889 case SF_ERR_SRVTO: /* ETIMEDOUT */
1890 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02001891 if (conn)
1892 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001893 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001894 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001895 /* should share same code than cases below */
1896 case SF_ERR_CHK_PORT:
1897 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02001898 case SF_ERR_PRXCOND:
1899 case SF_ERR_RESOURCE:
1900 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02001901 if (conn)
1902 conn->flags |= CO_FL_ERROR;
1903 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001904 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001905 }
1906
Willy Tarreau5a78f362012-11-23 12:47:05 +01001907 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01001908 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001909 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001910 if (check->wait_list.events)
1911 cs->conn->xprt->unsubscribe(cs->conn,
1912 cs->conn->xprt_ctx,
1913 check->wait_list.events,
1914 &check->wait_list);
1915 /* We may have been scheduled to run, and the
1916 * I/O handler expects to have a cs, so remove
1917 * the tasklet
1918 */
Willy Tarreau86eded62019-06-14 14:47:49 +02001919 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001920 cs_destroy(cs);
1921 cs = check->cs = NULL;
1922 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02001923 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001924
Willy Tarreau2c115e52013-12-11 19:41:16 +01001925 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02001926 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001927
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001928 /* we allow up to min(inter, timeout.connect) for a connection
1929 * to establish but only when timeout.check is set
1930 * as it may be to short for a full check otherwise
1931 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001932 while (tick_is_expired(t->expire, now_ms)) {
1933 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001934
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001935 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09001936 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001937 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001938 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001939 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001940 }
1941 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02001942 /* there was a test running.
1943 * First, let's check whether there was an uncaught error,
1944 * which can happen on connect timeout or error.
1945 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09001946 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001947 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001948 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001949 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09001950 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02001951 else
Simon Horman4a741432013-02-23 15:35:38 +09001952 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001953 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001954 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001955 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02001956 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001957 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001958 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02001959 }
1960
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001961 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001962
1963 check->current_step = NULL;
1964 if (check->sess != NULL) {
1965 session_free(check->sess);
1966 check->sess = NULL;
1967 }
1968
Willy Tarreau00149122017-10-04 18:05:01 +02001969 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001970 /* The check was aborted and the connection was not yet closed.
1971 * This can happen upon timeout, or when an external event such
1972 * as a failed response coupled with "observe layer7" caused the
1973 * server state to be suddenly changed.
1974 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001975 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001976 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001977 }
1978
Willy Tarreauac59f362017-10-08 11:10:19 +02001979 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001980 if (check->wait_list.events)
1981 cs->conn->xprt->unsubscribe(cs->conn,
1982 cs->conn->xprt_ctx,
1983 check->wait_list.events,
1984 &check->wait_list);
1985 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02001986 * I/O handler expects to have a cs, so remove
1987 * the tasklet
1988 */
1989 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001990 cs_destroy(cs);
1991 cs = check->cs = NULL;
1992 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02001993 }
1994
Olivier Houchard0923fa42019-01-11 18:43:04 +01001995 if (check->server) {
1996 if (check->result == CHK_RES_FAILED) {
1997 /* a failure or timeout detected */
1998 check_notify_failure(check);
1999 }
2000 else if (check->result == CHK_RES_CONDPASS) {
2001 /* check is OK but asks for stopping mode */
2002 check_notify_stopping(check);
2003 }
2004 else if (check->result == CHK_RES_PASSED) {
2005 /* a success was detected */
2006 check_notify_success(check);
2007 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002008 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02002009 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002010 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002011
Olivier Houchard0923fa42019-01-11 18:43:04 +01002012 if (check->server) {
2013 rv = 0;
2014 if (global.spread_checks > 0) {
2015 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002016 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01002017 }
2018 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02002019 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002020 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01002021
2022 reschedule:
2023 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09002024 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002025 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01002026 if (check->server)
2027 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01002028 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002029}
2030
Simon Horman98637e52014-06-20 12:30:16 +09002031/*
2032 * manages a server health-check. Returns
2033 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
2034 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002035static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09002036{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002037 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09002038
2039 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02002040 return process_chk_proc(t, context, state);
2041 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02002042
Simon Horman98637e52014-06-20 12:30:16 +09002043}
2044
Simon Horman5c942422013-11-25 10:46:32 +09002045static int start_check_task(struct check *check, int mininter,
2046 int nbcheck, int srvpos)
2047{
2048 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002049 unsigned long thread_mask = MAX_THREADS_MASK;
2050
2051 if (check->type == PR_O2_EXT_CHK)
2052 thread_mask = 1;
2053
Simon Horman5c942422013-11-25 10:46:32 +09002054 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002055 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002056 ha_alert("Starting [%s:%s] check: out of memory.\n",
2057 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002058 return 0;
2059 }
2060
2061 check->task = t;
2062 t->process = process_chk;
2063 t->context = check;
2064
Willy Tarreau1746eec2014-04-25 10:46:47 +02002065 if (mininter < srv_getinter(check))
2066 mininter = srv_getinter(check);
2067
2068 if (global.max_spread_checks && mininter > global.max_spread_checks)
2069 mininter = global.max_spread_checks;
2070
Simon Horman5c942422013-11-25 10:46:32 +09002071 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002072 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002073 check->start = now;
2074 task_queue(t);
2075
2076 return 1;
2077}
2078
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002079/*
2080 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002081 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002082 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002083static int start_checks()
2084{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002085
2086 struct proxy *px;
2087 struct server *s;
2088 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002089 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002090
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002091 /* 0- init the dummy frontend used to create all checks sessions */
2092 init_new_proxy(&checks_fe);
2093 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2094 checks_fe.mode = PR_MODE_TCP;
2095 checks_fe.maxconn = 0;
2096 checks_fe.conn_retries = CONN_RETRIES;
2097 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2098 checks_fe.timeout.client = TICK_ETERNITY;
2099
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002100 /* 1- count the checkers to run simultaneously.
2101 * We also determine the minimum interval among all of those which
2102 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2103 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002104 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002105 * too short an interval for all others.
2106 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002107 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002108 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002109 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002110 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002111 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002112 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002113 }
2114 /* We need a warmup task that will be called when the server
2115 * state switches from down to up.
2116 */
2117 s->warmup = t;
2118 t->process = server_warmup;
2119 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002120 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002121 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002122 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 +01002123 }
2124
Willy Tarreaud8514a22013-12-11 21:10:14 +01002125 if (s->check.state & CHK_ST_CONFIGURED) {
2126 nbcheck++;
2127 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2128 (!mininter || mininter > srv_getinter(&s->check)))
2129 mininter = srv_getinter(&s->check);
2130 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002131
Willy Tarreaud8514a22013-12-11 21:10:14 +01002132 if (s->agent.state & CHK_ST_CONFIGURED) {
2133 nbcheck++;
2134 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2135 (!mininter || mininter > srv_getinter(&s->agent)))
2136 mininter = srv_getinter(&s->agent);
2137 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002138 }
2139 }
2140
Simon Horman4a741432013-02-23 15:35:38 +09002141 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002142 return 0;
2143
2144 srand((unsigned)time(NULL));
2145
2146 /*
2147 * 2- start them as far as possible from each others. For this, we will
2148 * start them after their interval set to the min interval divided by
2149 * the number of servers, weighted by the server's position in the list.
2150 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002151 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002152 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2153 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002154 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002155 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002156 }
2157 }
2158
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002159 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002160 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002161 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002162 if (s->check.type == PR_O2_EXT_CHK) {
2163 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002164 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002165 }
Simon Hormand60d6912013-11-25 10:46:36 +09002166 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002167 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002168 srvpos++;
2169 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002170
Simon Hormand60d6912013-11-25 10:46:36 +09002171 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002172 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002173 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002174 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002175 }
2176 srvpos++;
2177 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002178 }
2179 }
2180 return 0;
2181}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002182
2183/*
Willy Tarreau5b3a2022012-09-28 15:01:02 +02002184 * Perform content verification check on data in s->check.buffer buffer.
Willy Tarreaubd741542010-03-16 18:46:54 +01002185 * The buffer MUST be terminated by a null byte before calling this function.
2186 * Sets server status appropriately. The caller is responsible for ensuring
2187 * that the buffer contains at least 13 characters. If <done> is zero, we may
2188 * return 0 to indicate that data is required to decide of a match.
2189 */
2190static int httpchk_expect(struct server *s, int done)
2191{
Christopher Faulet1bc04c72017-10-29 20:14:08 +01002192 static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>";
Willy Tarreaubd741542010-03-16 18:46:54 +01002193 char status_code[] = "000";
2194 char *contentptr;
2195 int crlf;
2196 int ret;
2197
2198 switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
2199 case PR_O2_EXP_STS:
2200 case PR_O2_EXP_RSTS:
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002201 memcpy(status_code, b_head(&s->check.bi) + 9, 3);
2202 memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3);
Willy Tarreaubd741542010-03-16 18:46:54 +01002203
2204 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
2205 ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
2206 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002207 ret = regex_exec(s->proxy->expect_regex, status_code);
Willy Tarreaubd741542010-03-16 18:46:54 +01002208
2209 /* we necessarily have the response, so there are no partial failures */
2210 if (s->proxy->options2 & PR_O2_EXP_INV)
2211 ret = !ret;
2212
Simon Horman4a741432013-02-23 15:35:38 +09002213 set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
Willy Tarreaubd741542010-03-16 18:46:54 +01002214 break;
2215
2216 case PR_O2_EXP_STR:
2217 case PR_O2_EXP_RSTR:
2218 /* very simple response parser: ignore CR and only count consecutive LFs,
2219 * stop with contentptr pointing to first char after the double CRLF or
2220 * to '\0' if crlf < 2.
2221 */
2222 crlf = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002223 for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) {
Willy Tarreaubd741542010-03-16 18:46:54 +01002224 if (crlf >= 2)
2225 break;
2226 if (*contentptr == '\r')
2227 continue;
2228 else if (*contentptr == '\n')
2229 crlf++;
2230 else
2231 crlf = 0;
2232 }
2233
2234 /* Check that response contains a body... */
2235 if (crlf < 2) {
2236 if (!done)
2237 return 0;
2238
Simon Horman4a741432013-02-23 15:35:38 +09002239 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002240 "HTTP content check could not find a response body");
2241 return 1;
2242 }
2243
2244 /* Check that response body is not empty... */
2245 if (*contentptr == '\0') {
Willy Tarreaua164fb52011-04-13 09:32:41 +02002246 if (!done)
2247 return 0;
2248
Simon Horman4a741432013-02-23 15:35:38 +09002249 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002250 "HTTP content check found empty response body");
2251 return 1;
2252 }
2253
2254 /* Check the response content against the supplied string
2255 * or regex... */
2256 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
2257 ret = strstr(contentptr, s->proxy->expect_str) != NULL;
2258 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002259 ret = regex_exec(s->proxy->expect_regex, contentptr);
Willy Tarreaubd741542010-03-16 18:46:54 +01002260
2261 /* if we don't match, we may need to wait more */
2262 if (!ret && !done)
2263 return 0;
2264
2265 if (ret) {
2266 /* content matched */
2267 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002268 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002269 "HTTP check matched unwanted content");
2270 else
Simon Horman4a741432013-02-23 15:35:38 +09002271 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002272 "HTTP content check matched");
2273 }
2274 else {
2275 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002276 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002277 "HTTP check did not match unwanted content");
2278 else
Simon Horman4a741432013-02-23 15:35:38 +09002279 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002280 "HTTP content check did not match");
2281 }
2282 break;
2283 }
2284 return 1;
2285}
2286
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002287/*
2288 * return the id of a step in a send/expect session
2289 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002290static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002291{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002292 if (!rule)
2293 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002294
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002295 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002296 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002297 return 1;
2298
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002299 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002300 if (rule->index == 0 &&
2301 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002302 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002303 return 0;
2304
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002305 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002306}
2307
Christopher Faulet206368d2020-04-03 14:51:06 +02002308static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2309 int match, struct ist info)
2310{
2311 struct sample *smp;
2312
2313 if (istlen(info)) {
2314 chunk_strncat(msg, info.ptr, info.len);
2315 goto comment;
2316 }
2317 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2318 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2319 goto comment;
2320 }
2321
Christopher Faulet799f3a42020-04-07 12:06:14 +02002322 if (check->type == PR_O2_TCPCHK_CHK && (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
2323 goto comment;
2324
Christopher Faulet206368d2020-04-03 14:51:06 +02002325 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2326 switch (rule->expect.type) {
2327 case TCPCHK_EXPECT_STRING:
2328 chunk_appendf(msg, " '%s' at step %d", rule->expect.string, tcpcheck_get_step_id(check, rule));
2329 break;
2330 case TCPCHK_EXPECT_BINARY:
2331 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2332 break;
2333 case TCPCHK_EXPECT_REGEX:
2334 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2335 break;
2336 case TCPCHK_EXPECT_REGEX_BINARY:
2337 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2338
2339 /* If references to the matched text were made, divide the
2340 * offsets by 2 to match offset of the original response buffer.
2341 */
2342 if (rule->expect.with_capture) {
2343 int i;
2344
2345 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2346 pmatch[i].rm_so /= 2; /* at first matched char. */
2347 pmatch[i].rm_eo /= 2; /* at last matched char. */
2348 }
2349 }
2350 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002351 case TCPCHK_EXPECT_CUSTOM:
2352 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2353 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002354 case TCPCHK_EXPECT_UNDEF:
2355 /* Should never happen. */
2356 return;
2357 }
2358
2359 comment:
2360 if (rule->comment) {
2361 chunk_strcat(msg, " comment: ");
2362 if (rule->expect.with_capture) {
2363 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2364 if (ret != -1) /* ignore comment if too large */
2365 msg->data += ret;
2366 }
2367 else
2368 chunk_strcat(msg, rule->comment);
2369 }
2370
2371 if (rule->expect.status_expr) {
2372 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2373 rule->expect.status_expr, SMP_T_SINT);
2374 if (smp)
2375 check->code = smp->data.u.sint;
2376 }
2377
2378 *(b_tail(msg)) = '\0';
2379}
2380
2381static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2382 struct ist info)
2383{
2384 struct sample *smp;
2385
2386 if (istlen(info))
2387 chunk_strncat(msg, info.ptr, info.len);
2388 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2389 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2390 &rule->expect.onsuccess_fmt);
Christopher Faulet799f3a42020-04-07 12:06:14 +02002391 else if (check->type == PR_O2_TCPCHK_CHK && !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
Christopher Faulet206368d2020-04-03 14:51:06 +02002392 chunk_strcat(msg, "(tcp-check)");
2393
2394 if (rule->expect.status_expr) {
2395 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2396 rule->expect.status_expr, SMP_T_SINT);
2397 if (smp)
2398 check->code = smp->data.u.sint;
2399 }
2400
2401 *(b_tail(msg)) = '\0';
2402}
2403
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002404static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2405 unsigned int offset, int last_read)
2406{
2407 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2408 enum healthcheck_status status;
2409 struct buffer *msg = NULL;
2410 struct ist desc = ist(NULL);
2411 unsigned int err = 0, plen = 0;
2412
2413
2414 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2415 if (!last_read && b_data(&check->bi) < offset+4) {
2416 if (!last_read)
2417 goto wait_more_data;
2418
2419 /* invalid length or truncated response */
2420 status = HCHK_STATUS_L7RSP;
2421 goto error;
2422 }
2423
2424 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2425 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2426 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2427
2428 if (b_data(&check->bi) < offset+plen+4) {
2429 if (!last_read)
2430 goto wait_more_data;
2431
2432 /* invalid length or truncated response */
2433 status = HCHK_STATUS_L7RSP;
2434 goto error;
2435 }
2436
2437 if (*b_peek(&check->bi, offset+4) == '\xff') {
2438 /* MySQL Error packet always begin with field_count = 0xff */
2439 status = HCHK_STATUS_L7STS;
2440 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2441 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2442 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2443 goto error;
2444 }
2445
2446 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2447 /* Not the last rule, continue */
2448 goto out;
2449 }
2450
2451 /* We set the MySQL Version in description for information purpose
2452 * FIXME : it can be cool to use MySQL Version for other purpose,
2453 * like mark as down old MySQL server.
2454 */
Christopher Fauletec07e382020-04-07 14:56:26 +02002455 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002456
2457 out:
2458 free_trash_chunk(msg);
2459 return ret;
2460
2461 error:
2462 ret = TCPCHK_EVAL_STOP;
2463 check->code = err;
2464 msg = alloc_trash_chunk();
2465 if (msg)
2466 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2467 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2468 goto out;
2469
2470 wait_more_data:
2471 ret = TCPCHK_EVAL_WAIT;
2472 goto out;
2473}
2474
2475
2476static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2477{
2478 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2479}
2480
2481static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2482{
2483 unsigned int hslen = 0;
2484
2485 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2486 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2487 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2488
2489 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2490}
2491
Christopher Faulet1997eca2020-04-03 23:13:50 +02002492static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2493{
2494 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2495 enum healthcheck_status status;
2496 struct buffer *msg = NULL;
2497 struct ist desc = ist(NULL);
2498 unsigned short msglen = 0;
2499
2500 /* Check if the server speaks LDAP (ASN.1/BER)
2501 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2502 * http://tools.ietf.org/html/rfc4511
2503 */
2504 /* size of LDAPMessage */
2505 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2506
2507 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2508 * messageID: 0x02 0x01 0x01: INTEGER 1
2509 * protocolOp: 0x61: bindResponse
2510 */
2511 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2512 status = HCHK_STATUS_L7RSP;
2513 desc = ist("Not LDAPv3 protocol");
2514 goto error;
2515 }
2516
2517 /* size of bindResponse */
2518 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2519
2520 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2521 * ldapResult: 0x0a 0x01: ENUMERATION
2522 */
2523 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2524 status = HCHK_STATUS_L7RSP;
2525 desc = ist("Not LDAPv3 protocol");
2526 goto error;
2527 }
2528
2529 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2530 * resultCode
2531 */
2532 check->code = *(b_head(&check->bi) + msglen + 9);
2533 if (check->code) {
2534 status = HCHK_STATUS_L7STS;
2535 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2536 goto error;
2537 }
2538
Christopher Fauletec07e382020-04-07 14:56:26 +02002539 set_server_check_status(check, rule->expect.ok_status, "Success");
Christopher Faulet1997eca2020-04-03 23:13:50 +02002540
2541 out:
2542 free_trash_chunk(msg);
2543 return ret;
2544
2545 error:
2546 ret = TCPCHK_EVAL_STOP;
2547 msg = alloc_trash_chunk();
2548 if (msg)
2549 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2550 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2551 goto out;
2552
2553 wait_more_data:
2554 ret = TCPCHK_EVAL_WAIT;
2555 goto out;
2556}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002557
Christopher Faulet267b01b2020-04-04 10:27:09 +02002558
2559static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
2560{
2561 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2562 enum healthcheck_status status;
2563 struct buffer *msg = NULL;
2564 struct ist desc = ist(NULL);
2565 unsigned int framesz;
2566
2567
2568 memcpy(&framesz, b_head(&check->bi), 4);
2569 framesz = ntohl(framesz);
2570
2571 if (!last_read && b_data(&check->bi) < (4+framesz))
2572 goto wait_more_data;
2573
2574 memset(b_orig(&trash), 0, b_size(&trash));
2575 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
2576 status = HCHK_STATUS_L7RSP;
2577 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
2578 goto error;
2579 }
2580
Christopher Fauletec07e382020-04-07 14:56:26 +02002581 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Christopher Faulet267b01b2020-04-04 10:27:09 +02002582
2583 out:
2584 free_trash_chunk(msg);
2585 return ret;
2586
2587 error:
2588 ret = TCPCHK_EVAL_STOP;
2589 msg = alloc_trash_chunk();
2590 if (msg)
2591 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2592 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2593 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002594
2595 wait_more_data:
2596 ret = TCPCHK_EVAL_WAIT;
2597 goto out;
2598}
2599
2600static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
2601{
2602 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
2603 enum healthcheck_status status = HCHK_STATUS_CHECKED;
2604 const char *hs = NULL; /* health status */
2605 const char *as = NULL; /* admin status */
2606 const char *ps = NULL; /* performance status */
2607 const char *cs = NULL; /* maxconn */
2608 const char *err = NULL; /* first error to report */
2609 const char *wrn = NULL; /* first warning to report */
2610 char *cmd, *p;
2611
2612 /* We're getting an agent check response. The agent could
2613 * have been disabled in the mean time with a long check
2614 * still pending. It is important that we ignore the whole
2615 * response.
2616 */
2617 if (!(check->state & CHK_ST_ENABLED))
2618 goto out;
2619
2620 /* The agent supports strings made of a single line ended by the
2621 * first CR ('\r') or LF ('\n'). This line is composed of words
2622 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
2623 * line may optionally contained a description of a state change
2624 * after a sharp ('#'), which is only considered if a health state
2625 * is announced.
2626 *
2627 * Words may be composed of :
2628 * - a numeric weight suffixed by the percent character ('%').
2629 * - a health status among "up", "down", "stopped", and "fail".
2630 * - an admin status among "ready", "drain", "maint".
2631 *
2632 * These words may appear in any order. If multiple words of the
2633 * same category appear, the last one wins.
2634 */
2635
2636 p = b_head(&check->bi);
2637 while (*p && *p != '\n' && *p != '\r')
2638 p++;
2639
2640 if (!*p) {
2641 if (!last_read)
2642 goto wait_more_data;
2643
2644 /* at least inform the admin that the agent is mis-behaving */
2645 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
2646 goto out;
2647 }
2648
2649 *p = 0;
2650 cmd = b_head(&check->bi);
2651
2652 while (*cmd) {
2653 /* look for next word */
2654 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
2655 cmd++;
2656 continue;
2657 }
2658
2659 if (*cmd == '#') {
2660 /* this is the beginning of a health status description,
2661 * skip the sharp and blanks.
2662 */
2663 cmd++;
2664 while (*cmd == '\t' || *cmd == ' ')
2665 cmd++;
2666 break;
2667 }
2668
2669 /* find the end of the word so that we have a null-terminated
2670 * word between <cmd> and <p>.
2671 */
2672 p = cmd + 1;
2673 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
2674 p++;
2675 if (*p)
2676 *p++ = 0;
2677
2678 /* first, health statuses */
2679 if (strcasecmp(cmd, "up") == 0) {
2680 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
2681 status = HCHK_STATUS_L7OKD;
2682 hs = cmd;
2683 }
2684 else if (strcasecmp(cmd, "down") == 0) {
2685 check->server->check.health = 0;
2686 status = HCHK_STATUS_L7STS;
2687 hs = cmd;
2688 }
2689 else if (strcasecmp(cmd, "stopped") == 0) {
2690 check->server->check.health = 0;
2691 status = HCHK_STATUS_L7STS;
2692 hs = cmd;
2693 }
2694 else if (strcasecmp(cmd, "fail") == 0) {
2695 check->server->check.health = 0;
2696 status = HCHK_STATUS_L7STS;
2697 hs = cmd;
2698 }
2699 /* admin statuses */
2700 else if (strcasecmp(cmd, "ready") == 0) {
2701 as = cmd;
2702 }
2703 else if (strcasecmp(cmd, "drain") == 0) {
2704 as = cmd;
2705 }
2706 else if (strcasecmp(cmd, "maint") == 0) {
2707 as = cmd;
2708 }
2709 /* try to parse a weight here and keep the last one */
2710 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
2711 ps = cmd;
2712 }
2713 /* try to parse a maxconn here */
2714 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
2715 cs = cmd;
2716 }
2717 else {
2718 /* keep a copy of the first error */
2719 if (!err)
2720 err = cmd;
2721 }
2722 /* skip to next word */
2723 cmd = p;
2724 }
2725 /* here, cmd points either to \0 or to the beginning of a
2726 * description. Skip possible leading spaces.
2727 */
2728 while (*cmd == ' ' || *cmd == '\n')
2729 cmd++;
2730
2731 /* First, update the admin status so that we avoid sending other
2732 * possibly useless warnings and can also update the health if
2733 * present after going back up.
2734 */
2735 if (as) {
2736 if (strcasecmp(as, "drain") == 0)
2737 srv_adm_set_drain(check->server);
2738 else if (strcasecmp(as, "maint") == 0)
2739 srv_adm_set_maint(check->server);
2740 else
2741 srv_adm_set_ready(check->server);
2742 }
2743
2744 /* now change weights */
2745 if (ps) {
2746 const char *msg;
2747
2748 msg = server_parse_weight_change_request(check->server, ps);
2749 if (!wrn || !*wrn)
2750 wrn = msg;
2751 }
2752
2753 if (cs) {
2754 const char *msg;
2755
2756 cs += strlen("maxconn:");
2757
2758 msg = server_parse_maxconn_change_request(check->server, cs);
2759 if (!wrn || !*wrn)
2760 wrn = msg;
2761 }
2762
2763 /* and finally health status */
2764 if (hs) {
2765 /* We'll report some of the warnings and errors we have
2766 * here. Down reports are critical, we leave them untouched.
2767 * Lack of report, or report of 'UP' leaves the room for
2768 * ERR first, then WARN.
2769 */
2770 const char *msg = cmd;
2771 struct buffer *t;
2772
2773 if (!*msg || status == HCHK_STATUS_L7OKD) {
2774 if (err && *err)
2775 msg = err;
2776 else if (wrn && *wrn)
2777 msg = wrn;
2778 }
2779
2780 t = get_trash_chunk();
2781 chunk_printf(t, "via agent : %s%s%s%s",
2782 hs, *msg ? " (" : "",
2783 msg, *msg ? ")" : "");
2784 set_server_check_status(check, status, t->area);
2785 }
2786 else if (err && *err) {
2787 /* No status change but we'd like to report something odd.
2788 * Just report the current state and copy the message.
2789 */
2790 chunk_printf(&trash, "agent reports an error : %s", err);
2791 set_server_check_status(check, status/*check->status*/, trash.area);
2792 }
2793 else if (wrn && *wrn) {
2794 /* No status change but we'd like to report something odd.
2795 * Just report the current state and copy the message.
2796 */
2797 chunk_printf(&trash, "agent warns : %s", wrn);
2798 set_server_check_status(check, status/*check->status*/, trash.area);
2799 }
2800 else
2801 set_server_check_status(check, status, NULL);
2802
2803 out:
2804 return ret;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002805
2806 wait_more_data:
2807 ret = TCPCHK_EVAL_WAIT;
2808 goto out;
2809}
2810
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002811/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2812 * to wait and -1 to stop the check. */
2813static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002814{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002815 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2816 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002817 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002818 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002819 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002820 struct conn_stream *cs;
2821 struct connection *conn = NULL;
2822 struct protocol *proto;
2823 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002824 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002825
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002826 /* For a connect action we'll create a new connection. We may also have
2827 * to kill a previous one. But we don't want to leave *without* a
2828 * connection if we came here from the connection layer, hence with a
2829 * connection. Thus we'll proceed in the following order :
2830 * 1: close but not release previous connection (handled by the caller)
2831 * 2: try to get a new connection
2832 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002833 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002834
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002835 /* 2- prepare new connection */
2836 cs = cs_new(NULL);
2837 if (!cs) {
2838 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2839 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002840 if (rule->comment)
2841 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002842 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2843 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002844 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002845 }
2846
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002847 /* 3- release and replace the old one on success */
2848 if (check->cs) {
2849 if (check->wait_list.events)
2850 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2851 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002852
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002853 /* We may have been scheduled to run, and the I/O handler
2854 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002855 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002856 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2857 cs_destroy(check->cs);
2858 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002859
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002860 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002861
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002862 check->cs = cs;
2863 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002864
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002865 /* Maybe there were an older connection we were waiting on */
2866 check->wait_list.events = 0;
2867 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002868
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002869 /* no client address */
2870 if (!sockaddr_alloc(&conn->dst)) {
2871 status = SF_ERR_RESOURCE;
2872 goto fail_check;
2873 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002874
Christopher Faulet5c288742020-03-31 08:15:58 +02002875 /* connect to the connect rule addr if specified, otherwise the check
2876 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002877 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002878 *conn->dst = (is_addr(&connect->addr)
2879 ? connect->addr
2880 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002881 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002882
Christopher Faulet5c288742020-03-31 08:15:58 +02002883 port = 0;
2884 if (!port && connect->port)
2885 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002886 if (!port && connect->port_expr) {
2887 struct sample *smp;
2888
2889 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2890 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2891 connect->port_expr, SMP_T_SINT);
2892 if (smp)
2893 port = smp->data.u.sint;
2894 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002895 if (!port && is_inet_addr(&connect->addr))
2896 port = get_host_port(&connect->addr);
2897 if (!port && check->port)
2898 port = check->port;
2899 if (!port && is_inet_addr(&check->addr))
2900 port = get_host_port(&check->addr);
2901 if (!port)
2902 port = s->svc_port;
2903 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002904
Christopher Fauletbb591a12020-04-01 16:52:17 +02002905 xprt = ((connect->options & TCPCHK_OPT_SSL)
2906 ? xprt_get(XPRT_SSL)
2907 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002908
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002909 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002910 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002911 status = SF_ERR_RESOURCE;
2912 goto fail_check;
2913 }
2914 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002915
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002916 status = SF_ERR_INTERNAL;
2917 if (proto && proto->connect) {
2918 struct tcpcheck_rule *next;
2919 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002920
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002921 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2922 if (!next || next->action != TCPCHK_ACT_EXPECT)
2923 flags |= CONNECT_DELACK_ALWAYS;
2924 status = proto->connect(conn, flags);
2925 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002926
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002927#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002928 if (status == SF_ERR_NONE) {
2929 if (connect->sni)
2930 ssl_sock_set_servername(conn, connect->sni);
2931 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2932 ssl_sock_set_servername(conn, s->check.sni);
2933
2934 if (connect->alpn)
2935 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2936 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2937 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002938 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002939#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002940 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2941 conn->send_proxy_ofs = 1;
2942 conn->flags |= CO_FL_SOCKS4;
2943 }
2944 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2945 conn->send_proxy_ofs = 1;
2946 conn->flags |= CO_FL_SOCKS4;
2947 }
2948
2949 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2950 conn->send_proxy_ofs = 1;
2951 conn->flags |= CO_FL_SEND_PROXY;
2952 }
2953 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2954 conn->send_proxy_ofs = 1;
2955 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002956 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002957
Christopher Fauletbb591a12020-04-01 16:52:17 +02002958 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2959 /* Some servers don't like reset on close */
2960 fdtab[cs->conn->handle.fd].linger_risk = 0;
2961 }
2962
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002963 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2964 if (xprt_add_hs(conn) < 0)
2965 status = SF_ERR_RESOURCE;
2966 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002967
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002968 fail_check:
2969 /* It can return one of :
2970 * - SF_ERR_NONE if everything's OK
2971 * - SF_ERR_SRVTO if there are no more servers
2972 * - SF_ERR_SRVCL if the connection was refused by the server
2973 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2974 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2975 * - SF_ERR_INTERNAL for any other purely internal errors
2976 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2977 * Note that we try to prevent the network stack from sending the ACK during the
2978 * connect() when a pure TCP check is used (without PROXY protocol).
2979 */
2980 switch (status) {
2981 case SF_ERR_NONE:
2982 /* we allow up to min(inter, timeout.connect) for a connection
2983 * to establish but only when timeout.check is set as it may be
2984 * to short for a full check otherwise
2985 */
2986 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002987
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002988 if (proxy->timeout.check && proxy->timeout.connect) {
2989 int t_con = tick_add(now_ms, proxy->timeout.connect);
2990 t->expire = tick_first(t->expire, t_con);
2991 }
2992 break;
2993 case SF_ERR_SRVTO: /* ETIMEDOUT */
2994 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
2995 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
2996 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002997 if (rule->comment)
2998 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002999 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
3000 ret = TCPCHK_EVAL_STOP;
3001 goto out;
3002 case SF_ERR_PRXCOND:
3003 case SF_ERR_RESOURCE:
3004 case SF_ERR_INTERNAL:
3005 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
3006 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003007 if (rule->comment)
3008 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003009 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3010 ret = TCPCHK_EVAL_STOP;
3011 goto out;
3012 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003013
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003014 /* don't do anything until the connection is established */
3015 if (conn->flags & CO_FL_WAIT_XPRT) {
3016 ret = TCPCHK_EVAL_WAIT;
3017 goto out;
3018 }
Willy Tarreaube373152018-09-06 11:45:30 +02003019
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003020 out:
3021 if (conn && check->result == CHK_RES_FAILED)
3022 conn->flags |= CO_FL_ERROR;
3023 return ret;
3024}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02003025
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003026/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
3027 * to wait and -1 to stop the check. */
3028static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
3029{
3030 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3031 struct tcpcheck_send *send = &rule->send;
3032 struct conn_stream *cs = check->cs;
3033 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003034 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003035
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003036 /* reset the read & write buffer */
3037 b_reset(&check->bi);
3038 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01003039
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003040 switch (send->type) {
3041 case TCPCHK_SEND_STRING:
3042 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003043 if (istlen(send->data) >= b_size(&check->bo)) {
3044 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
3045 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
3046 tcpcheck_get_step_id(check, rule));
3047 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3048 ret = TCPCHK_EVAL_STOP;
3049 goto out;
3050 }
3051 b_putist(&check->bo, send->data);
3052 break;
3053 case TCPCHK_SEND_STRING_LF:
3054 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
3055 if (!b_data(&check->bo))
3056 goto out;
3057 break;
3058 case TCPCHK_SEND_BINARY_LF:
3059 tmp = alloc_trash_chunk();
3060 if (!tmp)
3061 goto error_lf;
3062 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
3063 if (!b_data(tmp))
3064 goto out;
3065 tmp->area[tmp->data] = '\0';
3066 b_set_data(&check->bo, b_size(&check->bo));
3067 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
3068 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003069 break;
3070 case TCPCHK_SEND_UNDEF:
3071 /* Should never happen. */
3072 ret = TCPCHK_EVAL_STOP;
3073 goto out;
3074 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003075
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003076 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
3077 ret = TCPCHK_EVAL_WAIT;
3078 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
3079 ret = TCPCHK_EVAL_STOP;
3080 goto out;
3081 }
3082 if (b_data(&check->bo)) {
3083 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3084 ret = TCPCHK_EVAL_WAIT;
3085 goto out;
3086 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003087
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003088 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003089 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003090 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003091
3092 error_lf:
3093 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
3094 tcpcheck_get_step_id(check, rule));
3095 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3096 ret = TCPCHK_EVAL_STOP;
3097 goto out;
3098
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003099}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003100
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003101/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003102 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003103 */
3104static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3105{
3106 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003107 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003108 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003109 int match;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003110
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003111 /* The current expect might need more data than the previous one, check again
3112 * that the minimum amount data required to match is respected.
3113 */
3114 if (!last_read) {
3115 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
3116 (b_data(&check->bi) < expect->length)) {
3117 ret = TCPCHK_EVAL_WAIT;
3118 goto out;
3119 }
3120 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3121 ret = TCPCHK_EVAL_WAIT;
3122 goto out;
3123 }
3124 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003125
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003126 /* Make GCC happy ; initialize match to a failure state. */
3127 match = expect->inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003128
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003129 switch (expect->type) {
3130 case TCPCHK_EXPECT_STRING:
3131 case TCPCHK_EXPECT_BINARY:
3132 match = my_memmem(b_head(&check->bi), b_data(&check->bi), expect->string, expect->length) != NULL;
3133 break;
3134 case TCPCHK_EXPECT_REGEX:
3135 if (expect->with_capture)
3136 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3137 MAX_MATCH, pmatch, 0);
3138 else
3139 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3140 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003141
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003142 case TCPCHK_EXPECT_REGEX_BINARY:
3143 chunk_reset(&trash);
3144 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
3145 if (expect->with_capture)
3146 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3147 MAX_MATCH, pmatch, 0);
3148 else
3149 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3150 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003151 case TCPCHK_EXPECT_CUSTOM:
3152 if (expect->custom)
3153 ret = expect->custom(check, rule, last_read);
3154 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003155 case TCPCHK_EXPECT_UNDEF:
3156 /* Should never happen. */
3157 ret = TCPCHK_EVAL_STOP;
3158 goto out;
3159 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003160
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003161
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003162 /* Wait for more data on mismatch only if no minimum is defined (-1),
3163 * otherwise the absence of match is already conclusive.
3164 */
3165 if (!match && !last_read && (expect->min_recv == -1)) {
3166 ret = TCPCHK_EVAL_WAIT;
3167 goto out;
3168 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003169
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003170 /* Result as expected, next rule. */
3171 if (match ^ expect->inverse)
3172 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003173
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003174
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003175 /* From this point on, we matched something we did not want, this is an error state. */
3176 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003177 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003178 if (msg)
3179 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003180 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003181 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003182 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003183
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003184 out:
3185 return ret;
3186}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003187
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003188/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3189 * to wait and -1 to stop the check.
3190 */
3191static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3192{
3193 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3194 struct act_rule *act_rule;
3195 enum act_return act_ret;
3196
3197 act_rule =rule->action_kw.rule;
3198 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3199 if (act_ret != ACT_RET_CONT) {
3200 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3201 tcpcheck_get_step_id(check, rule));
3202 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3203 ret = TCPCHK_EVAL_STOP;
3204 }
3205
3206 return ret;
3207}
3208
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003209/* proceed with next steps for the TCP checks <check>. Note that this is called
3210 * both from the connection's wake() callback and from the check scheduling task.
3211 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3212 * connection, presenting the risk of an fd replacement.
3213 *
3214 * Please do NOT place any return statement in this function and only leave
3215 * via the out_end_tcpcheck label after setting retcode.
3216 */
3217static int tcpcheck_main(struct check *check)
3218{
3219 struct tcpcheck_rule *rule;
3220 struct conn_stream *cs = check->cs;
3221 struct connection *conn = cs_conn(cs);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003222 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003223 int must_read = 1, last_read = 0;
3224 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003225
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003226 /* here, we know that the check is complete or that it failed */
3227 if (check->result != CHK_RES_UNKNOWN)
3228 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003229
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003230 /* 1- check for connection error, if any */
3231 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3232 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003233
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003234 /* 2- check if we are waiting for the connection establishment. It only
3235 * happens during TCPCHK_ACT_CONNECT. */
3236 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3237 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003238
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003239 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3240 if (conn && b_data(&check->bo)) {
3241 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3242 if (ret <= 0) {
3243 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3244 goto out_end_tcpcheck;
3245 goto out;
3246 }
3247 if (b_data(&check->bo)) {
3248 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3249 goto out;
3250 }
3251 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003252
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003253 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003254
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003255 /* If check->current_step is defined, we are in resume condition. For
3256 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3257 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3258 * re-evaluate the current rule. Others cannot yield.
3259 */
3260 if (check->current_step) {
3261 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3262 check->current_step->action == TCPCHK_ACT_SEND)
3263 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3264 else
3265 rule = check->current_step;
3266 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003267 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003268 struct tcpcheck_var *var;
3269
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003270 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003271 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003272 if (!check->sess) {
3273 chunk_printf(&trash, "TCPCHK error allocating check session");
3274 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3275 goto out_end_tcpcheck;
3276 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003277 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003278 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003279
3280 /* Preset tcp-check variables */
3281 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3282 struct sample smp;
3283
3284 memset(&smp, 0, sizeof(smp));
3285 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3286 smp.data = var->data;
3287 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3288 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003289 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003290
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003291 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003292 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003293
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003294 switch (rule->action) {
3295 case TCPCHK_ACT_CONNECT:
3296 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003297
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003298 /* close but not release yet previous connection */
3299 if (check->cs) {
3300 cs_close(check->cs);
3301 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003302 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003303 eval_ret = tcpcheck_eval_connect(check, rule);
3304 must_read = 1; last_read = 0;
3305 break;
3306 case TCPCHK_ACT_SEND:
3307 check->current_step = rule;
3308 eval_ret = tcpcheck_eval_send(check, rule);
3309 must_read = 1;
3310 break;
3311 case TCPCHK_ACT_EXPECT:
3312 check->current_step = rule;
3313 if (must_read) {
3314 if (check->proxy->timeout.check)
3315 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003316
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003317 /* If we already subscribed, then we tried to received and
3318 * failed, so there's no point trying again.
3319 */
3320 if (check->wait_list.events & SUB_RETRY_RECV)
3321 goto out;
3322 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3323 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3324 last_read = 1;
3325 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3326 /* Report network errors only if we got no other data. Otherwise
3327 * we'll let the upper layers decide whether the response is OK
3328 * or not. It is very common that an RST sent by the server is
3329 * reported as an error just after the last data chunk.
3330 */
3331 goto out_end_tcpcheck;
3332 }
3333 }
3334 else {
3335 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3336 goto out;
3337 }
3338 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003339
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003340 /* buffer full, don't wait for more data */
3341 if (b_full(&check->bi))
3342 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003343
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003344 /* Check that response body is not empty... */
3345 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003346 if (!last_read)
3347 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003348
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003349 /* empty response */
3350 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3351 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003352 if (rule->comment)
3353 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003354 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003355 ret = -1;
3356 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003357 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003358 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003359 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003360
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003361 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3362 if (eval_ret == TCPCHK_EVAL_WAIT) {
3363 check->current_step = rule->expect.head;
3364 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003365 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003366 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003367 case TCPCHK_ACT_ACTION_KW:
3368 /* Don't update the current step */
3369 eval_ret = tcpcheck_eval_action_kw(check, rule);
3370 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003371 default:
3372 /* Otherwise, just go to the next one and don't update
3373 * the current step
3374 */
3375 eval_ret = TCPCHK_EVAL_CONTINUE;
3376 break;
3377 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003378
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003379 switch (eval_ret) {
3380 case TCPCHK_EVAL_CONTINUE:
3381 break;
3382 case TCPCHK_EVAL_WAIT:
3383 goto out;
3384 case TCPCHK_EVAL_STOP:
3385 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003386 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003387 }
3388
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003389 /* All rules was evaluated */
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003390 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet206368d2020-04-03 14:51:06 +02003391 msg = alloc_trash_chunk();
3392 if (msg)
3393 tcpcheck_onsuccess_message(msg, check, check->current_step, ist(NULL));
Christopher Fauletec07e382020-04-07 14:56:26 +02003394 set_server_check_status(check, check->current_step->expect.ok_status, (msg ? b_head(msg) : "(tcp-check)"));
3395 free_trash_chunk(msg);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003396 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003397 else
3398 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003399
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003400 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003401 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003402 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003403
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003404 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003405 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003406 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003407 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003408 session_free(check->sess);
3409 check->sess = NULL;
3410 }
3411 out:
3412 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003413}
3414
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003415static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003416{
3417 check->type = type;
3418
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003419 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3420 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003421
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003422 check->bi.area = calloc(check->bi.size, sizeof(char));
3423 check->bo.area = calloc(check->bo.size, sizeof(char));
3424
3425 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003426 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003427
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003428 check->wait_list.tasklet = tasklet_new();
3429 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003430 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003431 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003432 check->wait_list.tasklet->process = event_srv_chk_io;
3433 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003434 return NULL;
3435}
3436
Simon Hormanbfb5d332015-01-30 11:22:55 +09003437void free_check(struct check *check)
3438{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003439 task_destroy(check->task);
3440 if (check->wait_list.tasklet)
3441 tasklet_free(check->wait_list.tasklet);
3442
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003443 free(check->bi.area);
3444 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003445 if (check->cs) {
3446 free(check->cs->conn);
3447 check->cs->conn = NULL;
3448 cs_free(check->cs);
3449 check->cs = NULL;
3450 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003451}
3452
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003453static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3454{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003455 struct logformat_node *lf, *lfb;
3456
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003457 if (!rule)
3458 return;
3459
3460 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003461 switch (rule->action) {
3462 case TCPCHK_ACT_SEND:
3463 switch (rule->send.type) {
3464 case TCPCHK_SEND_STRING:
3465 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003466 free(rule->send.data.ptr);
3467 break;
3468 case TCPCHK_SEND_STRING_LF:
3469 case TCPCHK_SEND_BINARY_LF:
3470 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3471 LIST_DEL(&lf->list);
3472 release_sample_expr(lf->expr);
3473 free(lf->arg);
3474 free(lf);
3475 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003476 break;
3477 case TCPCHK_SEND_UNDEF:
3478 break;
3479 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003480 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003481 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003482 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3483 LIST_DEL(&lf->list);
3484 release_sample_expr(lf->expr);
3485 free(lf->arg);
3486 free(lf);
3487 }
3488 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3489 LIST_DEL(&lf->list);
3490 release_sample_expr(lf->expr);
3491 free(lf->arg);
3492 free(lf);
3493 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003494 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003495 switch (rule->expect.type) {
3496 case TCPCHK_EXPECT_STRING:
3497 case TCPCHK_EXPECT_BINARY:
3498 free(rule->expect.string);
3499 break;
3500 case TCPCHK_EXPECT_REGEX:
3501 case TCPCHK_EXPECT_REGEX_BINARY:
3502 regex_free(rule->expect.regex);
3503 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003504 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003505 case TCPCHK_EXPECT_UNDEF:
3506 break;
3507 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003508 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003509 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003510 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003511 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003512 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003513 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003514 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003515 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003516 case TCPCHK_ACT_ACTION_KW:
3517 free(rule->action_kw.rule);
3518 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003519 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003520
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003521 if (in_pool)
3522 pool_free(pool_head_tcpcheck_rule, rule);
3523 else
3524 free(rule);
3525}
3526
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003527
Christopher Fauletce355072020-04-02 11:44:39 +02003528static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003529{
3530 struct tcpcheck_var *var = NULL;
3531
3532 var = calloc(1, sizeof(*var));
3533 if (var == NULL)
3534 return NULL;
3535
3536 var->name = ist2(strdup(name), strlen(name));
3537 if (var->name.ptr == NULL) {
3538 free(var);
3539 return NULL;
3540 }
3541
3542 LIST_INIT(&var->list);
3543 return var;
3544}
3545
3546static void tcpcheck_var_release(struct tcpcheck_var *var)
3547{
3548 if (!var)
3549 return;
3550
3551 free(var->name.ptr);
3552 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3553 free(var->data.u.str.area);
3554 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3555 free(var->data.u.meth.str.area);
3556 free(var);
3557}
3558
3559int dup_tcpcheck_vars(struct list *dst, struct list *src)
3560{
3561 struct tcpcheck_var *var, *new = NULL;
3562
3563 list_for_each_entry(var, src, list) {
3564 new = tcpcheck_var_create(var->name.ptr);
3565 if (!new)
3566 goto error;
3567 new->data.type = var->data.type;
3568 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3569 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3570 goto error;
3571 if (var->data.type == SMP_T_STR)
3572 new->data.u.str.area[new->data.u.str.data] = 0;
3573 }
3574 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3575 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3576 goto error;
3577 new->data.u.str.area[new->data.u.str.data] = 0;
3578 new->data.u.meth.meth = var->data.u.meth.meth;
3579 }
3580 else
3581 new->data.u = var->data.u;
3582 LIST_ADDQ(dst, &new->list);
3583 }
3584 return 1;
3585
3586 error:
3587 free(new);
3588 return 0;
3589}
3590
3591static void free_tcpcheck_vars(struct list *vars)
3592{
3593 struct tcpcheck_var *var, *back;
3594
3595 list_for_each_entry_safe(var, back, vars, list) {
3596 LIST_DEL(&var->list);
3597 tcpcheck_var_release(var);
3598 }
3599}
3600
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003601void email_alert_free(struct email_alert *alert)
3602{
3603 struct tcpcheck_rule *rule, *back;
3604
3605 if (!alert)
3606 return;
3607
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003608 if (alert->rules.list) {
3609 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3610 LIST_DEL(&rule->list);
3611 free_tcpcheck(rule, 1);
3612 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003613 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003614 free(alert->rules.list);
3615 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003616 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003617 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003618}
3619
Olivier Houchard9f6af332018-05-25 14:04:04 +02003620static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003622 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003623 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003624 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003625
3626 q = container_of(check, typeof(*q), check);
3627
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003628 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003629 while (1) {
3630 if (!(check->state & CHK_ST_ENABLED)) {
3631 if (LIST_ISEMPTY(&q->email_alerts)) {
3632 /* All alerts processed, queue the task */
3633 t->expire = TICK_ETERNITY;
3634 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003635 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003636 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003637
3638 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003639 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003640 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003641 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003642 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003643 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003644 }
3645
Olivier Houchard9f6af332018-05-25 14:04:04 +02003646 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003647 if (check->state & CHK_ST_INPROGRESS)
3648 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003649
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003650 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003652 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003653 check->server = NULL;
3654 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003655 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003656 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003657 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003658 return t;
3659}
3660
Christopher Faulet0108bb32017-10-20 21:34:32 +02003661/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3662 *
3663 * The function returns 1 in success case, otherwise, it returns 0 and err is
3664 * filled.
3665 */
3666int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003667{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003668 struct mailer *mailer;
3669 struct email_alertq *queues;
3670 const char *err_str;
3671 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003672
Christopher Faulet0108bb32017-10-20 21:34:32 +02003673 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3674 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003675 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003676 }
3677
Christopher Faulet0108bb32017-10-20 21:34:32 +02003678 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3679 struct email_alertq *q = &queues[i];
3680 struct check *check = &q->check;
3681 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003682
3683 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003684 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003685 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003686 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003687 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003688 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003689 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3690 memprintf(err, "%s", err_str);
3691 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003692 }
3693
3694 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003695 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003696 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003697
Emeric Brunc60def82017-09-27 14:59:38 +02003698 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003699 memprintf(err, "out of memory while allocating mailer alerts task");
3700 goto error;
3701 }
3702
3703 check->task = t;
3704 t->process = process_email_alert;
3705 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003706
Christopher Faulet0108bb32017-10-20 21:34:32 +02003707 /* check this in one ms */
3708 t->expire = TICK_ETERNITY;
3709 check->start = now;
3710 task_queue(t);
3711 }
3712
3713 mls->users++;
3714 free(p->email_alert.mailers.name);
3715 p->email_alert.mailers.m = mls;
3716 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003717 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003718
3719 error:
3720 for (i = 0; i < mls->count; i++) {
3721 struct email_alertq *q = &queues[i];
3722 struct check *check = &q->check;
3723
Christopher Faulet0108bb32017-10-20 21:34:32 +02003724 free_check(check);
3725 }
3726 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003727 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003728 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729}
3730
3731
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003732static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003733{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003734 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003735 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003736
Willy Tarreaubafbe012017-11-24 17:34:44 +01003737 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003738 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003739 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003740 tcpcheck->action = TCPCHK_ACT_EXPECT;
3741
3742 expect = &tcpcheck->expect;
3743 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003744 LIST_INIT(&expect->onerror_fmt);
3745 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003746 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003747 expect->err_status = HCHK_STATUS_L7RSP;
3748 expect->tout_status = HCHK_STATUS_L7TOUT;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003749 expect->string = strdup(str);
3750 if (!expect->string) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003751 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003752 return 0;
3753 }
Gaetan Rivetb616add2020-02-07 15:37:17 +01003754 expect->length = strlen(expect->string);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003755
Gaetan Rivet4038b942020-02-26 16:19:40 +01003756 /* All tcp-check expect points back to the first inverse expect rule
3757 * in a chain of one or more expect rule, potentially itself.
3758 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003759 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003760 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003761 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Gaetan Rivetb616add2020-02-07 15:37:17 +01003762 if (prev_check->expect.inverse)
3763 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003764 continue;
3765 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003766 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003767 break;
3768 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003769 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003770 return 1;
3771}
3772
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003773static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003774{
3775 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003776 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003777 const char *in;
3778 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003779 int i;
3780
Willy Tarreaubafbe012017-11-24 17:34:44 +01003781 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003782 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003783 memset(tcpcheck, 0, sizeof(*tcpcheck));
3784 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003785
3786 send = &tcpcheck->send;
3787 send->type = TCPCHK_SEND_STRING;
3788
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003789 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003790 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003791
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003792 send->data.ptr = malloc(send->data.len + 1);
3793 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003794 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003795 return 0;
3796 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003797
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003798 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003799 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003800 for (in = strs[i]; (*dst = *in++); dst++);
3801 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003802
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003803 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003804 return 1;
3805}
3806
Christopher Faulet0108bb32017-10-20 21:34:32 +02003807static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3808 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003809{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003810 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003811 struct tcpcheck_rule *tcpcheck;
3812 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003813
Willy Tarreaubafbe012017-11-24 17:34:44 +01003814 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003816 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003817 alert->rules.flags = 0;
3818 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3819 if (!alert->rules.list)
3820 goto error;
3821 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003822 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003823 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003824
Willy Tarreaubafbe012017-11-24 17:34:44 +01003825 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003827 memset(tcpcheck, 0, sizeof(*tcpcheck));
3828 tcpcheck->action = TCPCHK_ACT_CONNECT;
3829 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003830
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003831 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003832
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003833 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003834 goto error;
3835
3836 {
3837 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003838 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003839 goto error;
3840 }
3841
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003842 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003843 goto error;
3844
3845 {
3846 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003847 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003848 goto error;
3849 }
3850
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003851 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003852 goto error;
3853
3854 {
3855 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003856 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003857 goto error;
3858 }
3859
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003860 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003861 goto error;
3862
3863 {
3864 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003865 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003866 goto error;
3867 }
3868
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003869 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003870 goto error;
3871
3872 {
3873 struct tm tm;
3874 char datestr[48];
3875 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003876 "From: ", p->email_alert.from, "\r\n",
3877 "To: ", p->email_alert.to, "\r\n",
3878 "Date: ", datestr, "\r\n",
3879 "Subject: [HAproxy Alert] ", msg, "\r\n",
3880 "\r\n",
3881 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003882 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003883 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003884 NULL
3885 };
3886
3887 get_localtime(date.tv_sec, &tm);
3888
3889 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3890 goto error;
3891 }
3892
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003893 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003894 goto error;
3895 }
3896
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003897 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003898 goto error;
3899
3900 {
3901 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003902 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003903 goto error;
3904 }
3905
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003906 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003907 goto error;
3908
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003909 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003910 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003911 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003912 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003913 return 1;
3914
3915error:
3916 email_alert_free(alert);
3917 return 0;
3918}
3919
Christopher Faulet0108bb32017-10-20 21:34:32 +02003920static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003921{
3922 int i;
3923 struct mailer *mailer;
3924
3925 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3926 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003927 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003928 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003929 return;
3930 }
3931 }
3932
3933 return;
3934}
3935
3936/*
3937 * Send email alert if configured.
3938 */
Simon Horman64e34162015-02-06 11:11:57 +09003939void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003940{
3941 va_list argp;
3942 char buf[1024];
3943 int len;
3944 struct proxy *p = s->proxy;
3945
Christopher Faulet0108bb32017-10-20 21:34:32 +02003946 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003947 return;
3948
3949 va_start(argp, format);
3950 len = vsnprintf(buf, sizeof(buf), format, argp);
3951 va_end(argp);
3952
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003953 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003954 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003955 return;
3956 }
3957
Christopher Faulet0108bb32017-10-20 21:34:32 +02003958 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003959}
3960
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003961/*
3962 * Return value:
3963 * the port to be used for the health check
3964 * 0 in case no port could be found for the check
3965 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003966static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003967{
3968 int i = 0;
3969 struct server *srv = NULL;
3970
3971 srv = chk->server;
3972
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003973 /* by default, we use the health check port ocnfigured */
3974 if (chk->port > 0)
3975 return chk->port;
3976
3977 /* try to get the port from check_core.addr if check.port not set */
3978 i = get_host_port(&chk->addr);
3979 if (i > 0)
3980 return i;
3981
3982 /* try to get the port from server address */
3983 /* prevent MAPPORTS from working at this point, since checks could
3984 * not be performed in such case (MAPPORTS impose a relative ports
3985 * based on live traffic)
3986 */
3987 if (srv->flags & SRV_F_MAPPORTS)
3988 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01003989
3990 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003991 if (i > 0)
3992 return i;
3993
3994 return 0;
3995}
3996
Willy Tarreau172f5ce2018-11-26 11:21:50 +01003997REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003998
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003999static int check_proxy_tcpcheck(struct proxy *px)
4000{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004001 struct tcpcheck_rule *chk, *back;
4002 char *comment = NULL;
4003 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004004 int ret = 0;
4005
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004006 if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004007 goto out;
4008
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004009 if (!px->tcpcheck_rules.list) {
4010 px->tcpcheck_rules.list = calloc(1, sizeof(*px->tcpcheck_rules.list));
4011 if (!px->tcpcheck_rules.list) {
4012 ha_alert("config : proxy '%s': out of memory.\n", px->id);
4013 ret |= ERR_ALERT | ERR_FATAL;
4014 goto out;
4015 }
4016 LIST_INIT(px->tcpcheck_rules.list);
4017 }
4018
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004019 /* If there is no connect rule preceeding all send / expect rules, an
4020 * implicit one is inserted before all others
4021 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004022 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004023 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4024 chk = calloc(1, sizeof(*chk));
4025 if (!chk) {
4026 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4027 "(out of memory).\n", px->id);
4028 ret |= ERR_ALERT | ERR_FATAL;
4029 goto out;
4030 }
4031 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004032 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004033 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004034 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004035
4036 /* Now remove comment rules */
4037 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4038 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4039 free(comment);
4040 comment = NULL;
4041 }
4042
4043 prev_action = chk->action;
4044 switch (chk->action) {
4045 case TCPCHK_ACT_COMMENT:
4046 free(comment);
4047 comment = chk->comment;
4048 LIST_DEL(&chk->list);
4049 free(chk);
4050 break;
4051 case TCPCHK_ACT_CONNECT:
4052 if (!chk->comment && comment)
4053 chk->comment = strdup(comment);
4054 /* fall though */
4055 case TCPCHK_ACT_ACTION_KW:
4056 free(comment);
4057 comment = NULL;
4058 break;
4059 case TCPCHK_ACT_SEND:
4060 case TCPCHK_ACT_EXPECT:
4061 if (!chk->comment && comment)
4062 chk->comment = strdup(comment);
4063 break;
4064 }
4065 }
4066 free(comment);
4067 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004068
4069 out:
4070 return ret;
4071}
4072
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004073static int init_srv_check(struct server *srv)
4074{
4075 const char *err;
4076 struct tcpcheck_rule *r;
4077 int ret = 0;
4078
4079 if (!srv->do_check)
4080 goto out;
4081
4082
4083 /* If neither a port nor an addr was specified and no check transport
4084 * layer is forced, then the transport layer used by the checks is the
4085 * same as for the production traffic. Otherwise we use raw_sock by
4086 * default, unless one is specified.
4087 */
4088 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4089 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4090 srv->check.use_ssl = srv->use_ssl;
4091 srv->check.xprt = srv->xprt;
4092 }
4093 else if (srv->check.use_ssl == 1)
4094 srv->check.xprt = xprt_get(XPRT_SSL);
4095
4096 srv->check.send_proxy |= (srv->pp_opts);
4097 }
4098
4099 /* validate <srv> server health-check settings */
4100
4101 /* We need at least a service port, a check port or the first tcp-check
4102 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4103 */
4104 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4105 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4106 goto init;
4107
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004108 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004109 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4110 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4111 ret |= ERR_ALERT | ERR_ABORT;
4112 goto out;
4113 }
4114
4115 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004116 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004117 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004118 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4119 "nor tcp_check rule 'connect' with port information.\n",
4120 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4121 ret |= ERR_ALERT | ERR_ABORT;
4122 goto out;
4123 }
4124
4125 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004126 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004127 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004128 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4129 "and a tcp_check rule 'connect' with no port information.\n",
4130 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4131 ret |= ERR_ALERT | ERR_ABORT;
4132 goto out;
4133 }
4134 }
4135
4136 init:
4137 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4138 if (err) {
4139 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4140 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4141 ret |= ERR_ALERT | ERR_ABORT;
4142 goto out;
4143 }
4144 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4145 global.maxsock++;
4146
4147 out:
4148 return ret;
4149}
4150
4151static int init_srv_agent_check(struct server *srv)
4152{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004153 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004154 const char *err;
4155 int ret = 0;
4156
4157 if (!srv->do_agent)
4158 goto out;
4159
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004160 /* If there is no connect rule preceeding all send / expect rules, an
4161 * implicit one is inserted before all others.
4162 */
4163 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4164 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4165 chk = calloc(1, sizeof(*chk));
4166 if (!chk) {
4167 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4168 " to agent-check for server '%s' (out of memory).\n",
4169 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4170 ret |= ERR_ALERT | ERR_FATAL;
4171 goto out;
4172 }
4173 chk->action = TCPCHK_ACT_CONNECT;
4174 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4175 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4176 }
4177
4178
4179 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004180 if (err) {
4181 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4182 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4183 ret |= ERR_ALERT | ERR_ABORT;
4184 goto out;
4185 }
4186
4187 if (!srv->agent.inter)
4188 srv->agent.inter = srv->check.inter;
4189
4190 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4191 global.maxsock++;
4192
4193 out:
4194 return ret;
4195}
4196
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004197void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004198{
4199 struct tcpcheck_rule *chk, *back;
4200
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004201 if (!px->tcpcheck_rules.list || (px->tcpcheck_rules.flags & TCPCHK_RULES_SHARED))
4202 goto end;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004203
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004204 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004205 LIST_DEL(&chk->list);
4206 free_tcpcheck(chk, 0);
4207 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004208 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004209 free(px->tcpcheck_rules.list);
4210
4211 end:
4212 px->tcpcheck_rules.flags = 0;
4213 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004214}
4215
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004216static void deinit_srv_check(struct server *srv)
4217{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004218 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004219 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004220 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4221 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004222}
4223
4224
4225static void deinit_srv_agent_check(struct server *srv)
4226{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004227 if (srv->agent.tcpcheck_rules) {
4228 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4229 free(srv->agent.tcpcheck_rules);
4230 srv->agent.tcpcheck_rules = NULL;
4231 }
4232
4233 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004234 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004235
4236 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4237 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004238}
4239
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004240static void deinit_tcpchecks()
4241{
4242 struct tcpcheck_ruleset *rs, *rsb;
4243 struct tcpcheck_rule *r, *rb;
4244
4245 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4246 LIST_DEL(&rs->list);
4247 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4248 LIST_DEL(&r->list);
4249 free_tcpcheck(r, 0);
4250 }
4251 free(rs->name);
4252 free(rs);
4253 }
4254}
4255
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004256
4257REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004258REGISTER_POST_SERVER_CHECK(init_srv_check);
4259REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4260
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004261REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004262REGISTER_SERVER_DEINIT(deinit_srv_check);
4263REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004264REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004265
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004266/* extracts check payload at a fixed position and length */
4267static int
4268smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4269{
4270 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4271 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4272 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4273 struct buffer *buf;
4274
4275 if (!srv || !srv->do_check)
4276 return 0;
4277
4278 buf = &srv->check.bi;
4279 if (buf_offset > b_data(buf))
4280 goto no_match;
4281 if (buf_offset + buf_size > b_data(buf))
4282 buf_size = 0;
4283
4284 /* init chunk as read only */
4285 smp->data.type = SMP_T_STR;
4286 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4287 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4288
4289 return 1;
4290
4291 no_match:
4292 smp->flags = 0;
4293 return 0;
4294}
4295
4296static struct sample_fetch_kw_list smp_kws = {ILH, {
4297 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4298 { /* END */ },
4299}};
4300
4301INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4302
4303
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004304struct action_kw_list tcp_check_keywords = {
4305 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4306};
4307
4308/* Return the struct action_kw associated to a keyword */
4309static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4310{
4311 return action_lookup(&tcp_check_keywords.list, kw);
4312}
4313
4314static void action_kw_tcp_check_build_list(struct buffer *chk)
4315{
4316 action_build_list(&tcp_check_keywords.list, chk);
4317}
4318
4319/* Create a tcp-check rule resulting from parsing a custom keyword. */
4320static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004321 struct list *rules, struct action_kw *kw,
4322 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004323{
4324 struct tcpcheck_rule *chk = NULL;
4325 struct act_rule *actrule = NULL;
4326
4327 actrule = calloc(1, sizeof(*actrule));
4328 if (!actrule) {
4329 memprintf(errmsg, "out of memory");
4330 goto error;
4331 }
4332 actrule->kw = kw;
4333 actrule->from = ACT_F_TCP_CHK;
4334
4335 cur_arg++;
4336 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4337 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4338 goto error;
4339 }
4340
4341 chk = calloc(1, sizeof(*chk));
4342 if (!chk) {
4343 memprintf(errmsg, "out of memory");
4344 goto error;
4345 }
4346 chk->action = TCPCHK_ACT_ACTION_KW;
4347 chk->action_kw.rule = actrule;
4348 return chk;
4349
4350 error:
4351 free(actrule);
4352 return NULL;
4353}
4354
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004355static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004356 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004357{
4358 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004359 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004360 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004361 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004362 unsigned short conn_opts = 0;
4363 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004364 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004365
4366 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004367 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004368 break;
4369 }
4370 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004371 memprintf(errmsg, "first step MUST also be a 'connect', "
4372 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4373 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004374 goto error;
4375 }
4376
4377 cur_arg++;
4378 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004379 if (strcmp(args[cur_arg], "default") == 0)
4380 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004381 else if (strcmp(args[cur_arg], "addr") == 0) {
4382 int port1, port2;
4383 struct protocol *proto;
4384
4385 if (!*(args[cur_arg+1])) {
4386 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4387 goto error;
4388 }
4389
4390 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4391 if (!sk) {
4392 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4393 goto error;
4394 }
4395
4396 proto = protocol_by_family(sk->ss_family);
4397 if (!proto || !proto->connect) {
4398 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4399 args[cur_arg]);
4400 goto error;
4401 }
4402
4403 if (port1 != port2) {
4404 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4405 args[cur_arg], args[cur_arg+1]);
4406 goto error;
4407 }
4408
4409 cur_arg++;
4410 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004411 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004412 const char *p, *end;
4413
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004414 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004415 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004416 goto error;
4417 }
4418 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004419
4420 port = 0;
4421 release_sample_expr(port_expr);
4422 p = args[cur_arg]; end = p + strlen(p);
4423 port = read_uint(&p, end);
4424 if (p != end) {
4425 int idx = 0;
4426
4427 px->conf.args.ctx = ARGC_SRV;
4428 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4429 file, line, errmsg, &px->conf.args, NULL);
4430
4431 if (!port_expr) {
4432 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4433 goto error;
4434 }
4435 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4436 memprintf(errmsg, "error detected while parsing port expression : "
4437 " fetch method '%s' extracts information from '%s', "
4438 "none of which is available here.\n",
4439 args[cur_arg], sample_src_names(port_expr->fetch->use));
4440 goto error;
4441 }
4442 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4443 }
4444 else if (port > 65535 || port < 1) {
4445 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4446 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004447 goto error;
4448 }
4449 }
4450 else if (strcmp(args[cur_arg], "comment") == 0) {
4451 if (!*(args[cur_arg+1])) {
4452 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4453 goto error;
4454 }
4455 cur_arg++;
4456 free(comment);
4457 comment = strdup(args[cur_arg]);
4458 if (!comment) {
4459 memprintf(errmsg, "out of memory");
4460 goto error;
4461 }
4462 }
4463 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4464 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004465 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4466 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004467 else if (strcmp(args[cur_arg], "linger") == 0)
4468 conn_opts |= TCPCHK_OPT_LINGER;
4469#ifdef USE_OPENSSL
4470 else if (strcmp(args[cur_arg], "ssl") == 0) {
4471 px->options |= PR_O_TCPCHK_SSL;
4472 conn_opts |= TCPCHK_OPT_SSL;
4473 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004474 else if (strcmp(args[cur_arg], "sni") == 0) {
4475 if (!*(args[cur_arg+1])) {
4476 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4477 goto error;
4478 }
4479 cur_arg++;
4480 free(sni);
4481 sni = strdup(args[cur_arg]);
4482 if (!sni) {
4483 memprintf(errmsg, "out of memory");
4484 goto error;
4485 }
4486 }
Christopher Faulet98572322020-03-30 13:16:44 +02004487 else if (strcmp(args[cur_arg], "alpn") == 0) {
4488#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4489 free(alpn);
4490 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4491 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4492 goto error;
4493 }
4494 cur_arg++;
4495#else
4496 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4497 goto error;
4498#endif
4499 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004500#endif /* USE_OPENSSL */
4501
4502 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004503 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004504#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004505 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004506#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004507 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004508 args[cur_arg]);
4509 goto error;
4510 }
4511 cur_arg++;
4512 }
4513
4514 chk = calloc(1, sizeof(*chk));
4515 if (!chk) {
4516 memprintf(errmsg, "out of memory");
4517 goto error;
4518 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004519 chk->action = TCPCHK_ACT_CONNECT;
4520 chk->comment = comment;
4521 chk->connect.port = port;
4522 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004523 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004524 chk->connect.alpn = alpn;
4525 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004526 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004527 if (sk)
4528 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004529 return chk;
4530
4531 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004532 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004533 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004534 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004535 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004536 return NULL;
4537}
4538
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004539static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004540 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004541{
4542 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004543 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004544 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004545
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004546 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004547 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004548 memprintf(errmsg, "'%s' expects a %s as argument",
4549 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004550 goto error;
4551 }
4552
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004553 data = args[cur_arg+1];
4554
4555 cur_arg += 2;
4556 while (*(args[cur_arg])) {
4557 if (strcmp(args[cur_arg], "comment") == 0) {
4558 if (!*(args[cur_arg+1])) {
4559 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4560 goto error;
4561 }
4562 cur_arg++;
4563 free(comment);
4564 comment = strdup(args[cur_arg]);
4565 if (!comment) {
4566 memprintf(errmsg, "out of memory");
4567 goto error;
4568 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004569 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004570 else if (strcmp(args[cur_arg], "log-format") == 0) {
4571 if (type == TCPCHK_SEND_BINARY)
4572 type = TCPCHK_SEND_BINARY_LF;
4573 else if (type == TCPCHK_SEND_STRING)
4574 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004575 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004576 else {
4577 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4578 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004579 goto error;
4580 }
4581 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004582 }
4583
4584 chk = calloc(1, sizeof(*chk));
4585 if (!chk) {
4586 memprintf(errmsg, "out of memory");
4587 goto error;
4588 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004589 chk->action = TCPCHK_ACT_SEND;
4590 chk->comment = comment;
4591 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004592
4593 switch (chk->send.type) {
4594 case TCPCHK_SEND_STRING:
4595 chk->send.data = ist2(strdup(data), strlen(data));
4596 if (!isttest(chk->send.data)) {
4597 memprintf(errmsg, "out of memory");
4598 goto error;
4599 }
4600 break;
4601 case TCPCHK_SEND_BINARY:
4602 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4603 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4604 goto error;
4605 }
4606 break;
4607 case TCPCHK_SEND_STRING_LF:
4608 case TCPCHK_SEND_BINARY_LF:
4609 LIST_INIT(&chk->send.fmt);
4610 px->conf.args.ctx = ARGC_SRV;
4611 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4612 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4613 goto error;
4614 }
4615 break;
4616 case TCPCHK_SEND_UNDEF:
4617 goto error;
4618 }
4619
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004620 return chk;
4621
4622 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004623 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004624 free(comment);
4625 return NULL;
4626}
4627
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004628static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4629 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004630{
4631 struct tcpcheck_rule *chk = NULL;
4632 char *comment = NULL;
4633
4634 if (!*(args[cur_arg+1])) {
4635 memprintf(errmsg, "expects a string as argument");
4636 goto error;
4637 }
4638 cur_arg++;
4639 comment = strdup(args[cur_arg]);
4640 if (!comment) {
4641 memprintf(errmsg, "out of memory");
4642 goto error;
4643 }
4644
4645 chk = calloc(1, sizeof(*chk));
4646 if (!chk) {
4647 memprintf(errmsg, "out of memory");
4648 goto error;
4649 }
4650 chk->action = TCPCHK_ACT_COMMENT;
4651 chk->comment = comment;
4652 return chk;
4653
4654 error:
4655 free(comment);
4656 return NULL;
4657}
4658
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004659static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4660 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004661{
4662 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004663 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004664 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004665 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletec07e382020-04-07 14:56:26 +02004666 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004667 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4668 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004669 long min_recv = -1;
4670 int inverse = 0, with_capture = 0;
4671
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004672 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004673 if (!*(args[cur_arg+1])) {
4674 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004675 goto error;
4676 }
4677
4678 cur_arg++;
4679 while (*(args[cur_arg])) {
4680 int in_pattern = 0;
4681
4682 rescan:
4683 if (strcmp(args[cur_arg], "min-recv") == 0) {
4684 if (in_pattern) {
4685 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4686 goto error;
4687 }
4688 if (!*(args[cur_arg+1])) {
4689 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4690 goto error;
4691 }
4692 /* Use an signed integer here because of chksize */
4693 cur_arg++;
4694 min_recv = atol(args[cur_arg]);
4695 if (min_recv < -1 || min_recv > INT_MAX) {
4696 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4697 goto error;
4698 }
4699 }
4700 else if (*(args[cur_arg]) == '!') {
4701 in_pattern = 1;
4702 while (*(args[cur_arg]) == '!') {
4703 inverse = !inverse;
4704 args[cur_arg]++;
4705 }
4706 if (!*(args[cur_arg]))
4707 cur_arg++;
4708 goto rescan;
4709 }
4710 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4711 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4712 if (type != TCPCHK_EXPECT_UNDEF) {
4713 memprintf(errmsg, "only on pattern expected");
4714 goto error;
4715 }
4716 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4717 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4718 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4719
4720 if (!*(args[cur_arg+1])) {
4721 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4722 goto error;
4723 }
4724 cur_arg++;
4725 pattern = args[cur_arg];
4726 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004727 else if (strcmp(args[cur_arg], "custom") == 0) {
4728 if (in_pattern) {
4729 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4730 goto error;
4731 }
4732 if (type != TCPCHK_EXPECT_UNDEF) {
4733 memprintf(errmsg, "only on pattern expected");
4734 goto error;
4735 }
4736 type = TCPCHK_EXPECT_CUSTOM;
4737 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004738 else if (strcmp(args[cur_arg], "comment") == 0) {
4739 if (in_pattern) {
4740 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4741 goto error;
4742 }
4743 if (!*(args[cur_arg+1])) {
4744 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4745 goto error;
4746 }
4747 cur_arg++;
4748 free(comment);
4749 comment = strdup(args[cur_arg]);
4750 if (!comment) {
4751 memprintf(errmsg, "out of memory");
4752 goto error;
4753 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004754 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004755 else if (strcmp(args[cur_arg], "on-success") == 0) {
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 a string as argument", args[cur_arg]);
4762 goto error;
4763 }
4764 cur_arg++;
4765 free(on_success_msg);
4766 on_success_msg = strdup(args[cur_arg]);
4767 if (!on_success_msg) {
4768 memprintf(errmsg, "out of memory");
4769 goto error;
4770 }
4771 }
4772 else if (strcmp(args[cur_arg], "on-error") == 0) {
4773 if (in_pattern) {
4774 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4775 goto error;
4776 }
4777 if (!*(args[cur_arg+1])) {
4778 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4779 goto error;
4780 }
4781 cur_arg++;
4782 free(on_error_msg);
4783 on_error_msg = strdup(args[cur_arg]);
4784 if (!on_error_msg) {
4785 memprintf(errmsg, "out of memory");
4786 goto error;
4787 }
Christopher Fauletec07e382020-04-07 14:56:26 +02004788 }
4789 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4790 if (in_pattern) {
4791 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4792 goto error;
4793 }
4794 if (!*(args[cur_arg+1])) {
4795 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4796 goto error;
4797 }
4798 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4799 ok_st = HCHK_STATUS_L7OKD;
4800 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4801 ok_st = HCHK_STATUS_L7OKCD;
4802 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4803 ok_st = HCHK_STATUS_L6OK;
4804 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4805 ok_st = HCHK_STATUS_L4OK;
4806 else {
4807 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4808 args[cur_arg], args[cur_arg+1]);
4809 goto error;
4810 }
4811 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004812 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004813 else if (strcmp(args[cur_arg], "error-status") == 0) {
4814 if (in_pattern) {
4815 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4816 goto error;
4817 }
4818 if (!*(args[cur_arg+1])) {
4819 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4820 goto error;
4821 }
4822 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4823 err_st = HCHK_STATUS_L7RSP;
4824 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4825 err_st = HCHK_STATUS_L7STS;
4826 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4827 err_st = HCHK_STATUS_L6RSP;
4828 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4829 err_st = HCHK_STATUS_L4CON;
4830 else {
4831 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4832 args[cur_arg], args[cur_arg+1]);
4833 goto error;
4834 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004835 cur_arg++;
4836 }
4837 else if (strcmp(args[cur_arg], "status-code") == 0) {
4838 int idx = 0;
4839
4840 if (in_pattern) {
4841 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4842 goto error;
4843 }
4844 if (!*(args[cur_arg+1])) {
4845 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4846 goto error;
4847 }
4848
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004849 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004850 release_sample_expr(status_expr);
4851 px->conf.args.ctx = ARGC_SRV;
4852 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4853 file, line, errmsg, &px->conf.args, NULL);
4854 if (!status_expr) {
4855 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4856 goto error;
4857 }
4858 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4859 memprintf(errmsg, "error detected while parsing status-code expression : "
4860 " fetch method '%s' extracts information from '%s', "
4861 "none of which is available here.\n",
4862 args[cur_arg], sample_src_names(status_expr->fetch->use));
4863 goto error;
4864 }
4865 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004866 }
4867 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4868 if (in_pattern) {
4869 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4870 goto error;
4871 }
4872 if (!*(args[cur_arg+1])) {
4873 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4874 goto error;
4875 }
4876 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4877 tout_st = HCHK_STATUS_L7TOUT;
4878 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4879 tout_st = HCHK_STATUS_L6TOUT;
4880 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4881 tout_st = HCHK_STATUS_L4TOUT;
4882 else {
4883 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4884 args[cur_arg], args[cur_arg+1]);
4885 goto error;
4886 }
4887 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004888 }
4889 else {
4890 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4891 " or comment but got '%s' as argument.", args[cur_arg]);
4892 goto error;
4893 }
4894
4895 cur_arg++;
4896 }
4897
4898 if (comment) {
4899 char *p = comment;
4900
4901 while (*p) {
4902 if (*p == '\\') {
4903 p++;
4904 if (!*p || !isdigit((unsigned char)*p) ||
4905 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4906 memprintf(errmsg, "invalid backreference in 'comment' argument");
4907 goto error;
4908 }
4909 with_capture = 1;
4910 }
4911 p++;
4912 }
4913 if (with_capture && !inverse)
4914 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4915 }
4916
4917 chk = calloc(1, sizeof(*chk));
4918 if (!chk) {
4919 memprintf(errmsg, "out of memory");
4920 goto error;
4921 }
4922 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004923 LIST_INIT(&chk->expect.onerror_fmt);
4924 LIST_INIT(&chk->expect.onsuccess_fmt);
4925 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004926 chk->expect.type = type;
4927 chk->expect.min_recv = min_recv;
4928 chk->expect.inverse = inverse;
4929 chk->expect.with_capture = with_capture;
Christopher Fauletec07e382020-04-07 14:56:26 +02004930 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004931 chk->expect.err_status = err_st;
4932 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004933 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004934
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004935 if (on_success_msg) {
4936 px->conf.args.ctx = ARGC_SRV;
4937 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4938 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4939 goto error;
4940 }
4941 free(on_success_msg);
4942 on_success_msg = NULL;
4943 }
4944 if (on_error_msg) {
4945 px->conf.args.ctx = ARGC_SRV;
4946 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4947 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4948 goto error;
4949 }
4950 free(on_error_msg);
4951 on_error_msg = NULL;
4952 }
4953
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004954 switch (chk->expect.type) {
4955 case TCPCHK_EXPECT_STRING:
4956 chk->expect.string = strdup(pattern);
4957 chk->expect.length = strlen(pattern);
4958 if (!chk->expect.string) {
4959 memprintf(errmsg, "out of memory");
4960 goto error;
4961 }
4962 break;
4963 case TCPCHK_EXPECT_BINARY:
4964 if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
4965 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4966 goto error;
4967 }
4968 case TCPCHK_EXPECT_REGEX:
4969 case TCPCHK_EXPECT_REGEX_BINARY:
4970 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
4971 if (!chk->expect.regex)
4972 goto error;
4973 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004974 case TCPCHK_EXPECT_CUSTOM:
4975 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4976 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004977 case TCPCHK_EXPECT_UNDEF:
4978 free(chk);
4979 memprintf(errmsg, "pattern not found");
4980 goto error;
4981 }
4982
4983 /* All tcp-check expect points back to the first inverse expect rule in
4984 * a chain of one or more expect rule, potentially itself.
4985 */
4986 chk->expect.head = chk;
4987 list_for_each_entry_rev(prev_check, rules, list) {
4988 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4989 if (prev_check->expect.inverse)
4990 chk->expect.head = prev_check;
4991 continue;
4992 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01004993 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004994 break;
4995 }
4996 return chk;
4997
4998 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004999 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005000 free(str);
5001 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005002 free(on_success_msg);
5003 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005004 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005005 return NULL;
5006}
5007
5008/* Parses the "tcp-check" proxy keyword */
5009static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5010 struct proxy *defpx, const char *file, int line,
5011 char **errmsg)
5012{
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005013 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005014 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005015 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005016
5017 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5018 ret = 1;
5019
5020 if (curpx == defpx) {
5021 memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
5022 goto error;
5023 }
5024
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005025 if (rules->flags & TCPCHK_RULES_DEF) {
5026 /* Only shared ruleset can be inherited from the default section */
5027 rules->flags = 0;
5028 rules->list = NULL;
5029 }
5030 if (rules->list && (rules->flags & TCPCHK_RULES_SHARED)) {
5031 memprintf(errmsg, "%s : A shared tcp-check ruleset already configured.", args[0]);
5032 goto error;
5033 }
5034
5035 if (!rules->list) {
5036 rules->list = calloc(1, sizeof(*rules->list));
5037 if (!rules->list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005038 memprintf(errmsg, "%s : out of memory.", args[0]);
5039 goto error;
5040 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005041 LIST_INIT(rules->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005042 }
5043
Gaetan Rivet5301b012020-02-25 17:19:17 +01005044 index = 0;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005045 if (!LIST_ISEMPTY(rules->list)) {
5046 chk = LIST_PREV(rules->list, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005047 index = chk->index + 1;
5048 }
5049
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005050 cur_arg = 1;
5051 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005052 chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005053 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005054 chk = parse_tcpcheck_send(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005055 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005056 chk = parse_tcpcheck_expect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005057 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005058 chk = parse_tcpcheck_comment(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005059 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005060 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5061
5062 if (!kw) {
5063 action_kw_tcp_check_build_list(&trash);
5064 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5065 "%s%s. but got '%s'",
5066 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5067 goto error;
5068 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005069 chk = parse_tcpcheck_action(args, cur_arg, curpx, rules->list, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005070 }
5071
5072 if (!chk) {
5073 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5074 goto error;
5075 }
5076 ret = (*errmsg != NULL); /* Handle warning */
5077
5078 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005079 chk->index = index;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005080 LIST_ADDQ(rules->list, &chk->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005081 return ret;
5082
5083 error:
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005084 deinit_proxy_tcpcheck(curpx);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005085 return -1;
5086}
5087
Christopher Faulet33f05df2020-04-01 11:08:50 +02005088
5089static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
5090{
5091 struct tcpcheck_ruleset *rs;
5092
5093 list_for_each_entry(rs, &tcpchecks_list, list) {
5094 if (strcmp(rs->name, name) == 0)
5095 return rs;
5096 }
5097 return NULL;
5098}
5099
5100static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
5101{
5102 struct tcpcheck_ruleset *rs;
5103
5104 rs = calloc(1, sizeof(*rs));
5105 if (rs == NULL)
5106 return NULL;
5107
5108 rs->name = strdup(name);
5109 if (rs->name == NULL) {
5110 free(rs);
5111 return NULL;
5112 }
5113
5114 LIST_INIT(&rs->list);
5115 LIST_INIT(&rs->rules);
5116 LIST_ADDQ(&tcpchecks_list, &rs->list);
5117 return rs;
5118}
5119
5120static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
5121{
5122 struct tcpcheck_rule *r, *rb;
5123 if (!rs)
5124 return;
5125
5126 LIST_DEL(&rs->list);
5127 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5128 LIST_DEL(&r->list);
5129 free_tcpcheck(r, 0);
5130 }
5131 free(rs->name);
5132 free(rs);
5133}
5134
5135
5136/* Parses the "option redis-check" proxy keyword */
5137int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5138 const char *file, int line)
5139{
5140 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5141 static char *redis_res = "+PONG\r\n";
5142
5143 struct tcpcheck_ruleset *rs = NULL;
5144 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5145 struct tcpcheck_rule *chk;
5146 char *errmsg = NULL;
5147 int err_code = 0;
5148
5149 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5150 err_code |= ERR_WARN;
5151
5152 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5153 goto out;
5154
5155 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5156 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5157 file, line);
5158 err_code |= ERR_ALERT | ERR_FATAL;
5159 goto out;
5160 }
5161
5162 curpx->options2 &= ~PR_O2_CHK_ANY;
5163 curpx->options2 |= PR_O2_TCPCHK_CHK;
5164
5165 free_tcpcheck_vars(&rules->preset_vars);
5166 rules->list = NULL;
5167 rules->flags = 0;
5168
5169 rs = tcpcheck_ruleset_lookup("*redis-check");
5170 if (rs)
5171 goto ruleset_found;
5172
5173 rs = tcpcheck_ruleset_create("*redis-check");
5174 if (rs == NULL) {
5175 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5176 goto error;
5177 }
5178
5179 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5180 1, curpx, &rs->rules, file, line, &errmsg);
5181 if (!chk) {
5182 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5183 goto error;
5184 }
5185 chk->index = 0;
5186 LIST_ADDQ(&rs->rules, &chk->list);
5187
5188 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5189 "error-status", "L7STS",
5190 "on-error", "%[check.payload(),cut_crlf]",
5191 "on-success", "Redis server is ok",
5192 ""},
5193 1, curpx, &rs->rules, file, line, &errmsg);
5194 if (!chk) {
5195 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5196 goto error;
5197 }
5198 chk->index = 1;
5199 LIST_ADDQ(&rs->rules, &chk->list);
5200
5201 LIST_ADDQ(&tcpchecks_list, &rs->list);
5202
5203 ruleset_found:
5204 rules->list = &rs->rules;
5205 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_REDIS_CHK);
5206
5207 out:
5208 free(errmsg);
5209 return err_code;
5210
5211 error:
5212 tcpcheck_ruleset_release(rs);
5213 err_code |= ERR_ALERT | ERR_FATAL;
5214 goto out;
5215}
5216
Christopher Faulet811f78c2020-04-01 11:10:27 +02005217
5218/* Parses the "option ssl-hello-chk" proxy keyword */
5219int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5220 const char *file, int line)
5221{
5222 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5223 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5224 *
5225 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5226 */
5227 static char sslv3_client_hello[] = {
5228 "16" /* ContentType : 0x16 = Hanshake */
5229 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5230 "0079" /* ContentLength : 0x79 bytes after this one */
5231 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5232 "000075" /* HandshakeLength : 0x75 bytes after this one */
5233 "0300" /* Hello Version : 0x0300 = v3 */
5234 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5235 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5236 "00" /* Session ID length : empty (no session ID) */
5237 "004E" /* Cipher Suite Length : 78 bytes after this one */
5238 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5239 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5240 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5241 "000D" "000E" "000F" "0010" /* various bit lengths, */
5242 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5243 "0015" "0016" "0017" "0018"
5244 "0019" "001A" "001B" "002F"
5245 "0030" "0031" "0032" "0033"
5246 "0034" "0035" "0036" "0037"
5247 "0038" "0039" "003A"
5248 "01" /* Compression Length : 0x01 = 1 byte for types */
5249 "00" /* Compression Type : 0x00 = NULL compression */
5250 };
5251
5252 struct tcpcheck_ruleset *rs = NULL;
5253 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5254 struct tcpcheck_rule *chk;
5255 char *errmsg = NULL;
5256 int err_code = 0;
5257
5258 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5259 err_code |= ERR_WARN;
5260
5261 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5262 goto out;
5263
5264 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5265 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5266 file, line);
5267 err_code |= ERR_ALERT | ERR_FATAL;
5268 goto out;
5269 }
5270
5271 curpx->options2 &= ~PR_O2_CHK_ANY;
5272 curpx->options2 |= PR_O2_TCPCHK_CHK;
5273
5274 free_tcpcheck_vars(&rules->preset_vars);
5275 rules->list = NULL;
5276 rules->flags = 0;
5277
5278 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5279 if (rs)
5280 goto ruleset_found;
5281
5282 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5283 if (rs == NULL) {
5284 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5285 goto error;
5286 }
5287
5288 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5289 1, curpx, &rs->rules, file, line, &errmsg);
5290 if (!chk) {
5291 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5292 goto error;
5293 }
5294 chk->index = 0;
5295 LIST_ADDQ(&rs->rules, &chk->list);
5296
5297 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005298 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005299 "error-status", "L6RSP", "tout-status", "L6TOUT",
5300 ""},
5301 1, curpx, &rs->rules, file, line, &errmsg);
5302 if (!chk) {
5303 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5304 goto error;
5305 }
5306 chk->index = 1;
5307 LIST_ADDQ(&rs->rules, &chk->list);
5308
5309 LIST_ADDQ(&tcpchecks_list, &rs->list);
5310
5311 ruleset_found:
5312 rules->list = &rs->rules;
5313 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SSL3_CHK);
5314
5315 out:
5316 free(errmsg);
5317 return err_code;
5318
5319 error:
5320 tcpcheck_ruleset_release(rs);
5321 err_code |= ERR_ALERT | ERR_FATAL;
5322 goto out;
5323}
5324
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005325/* Parses the "option smtpchk" proxy keyword */
5326int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5327 const char *file, int line)
5328{
5329 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5330
5331 struct tcpcheck_ruleset *rs = NULL;
5332 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5333 struct tcpcheck_rule *chk;
5334 struct tcpcheck_var *var = NULL;
5335 char *cmd = NULL, *errmsg = NULL;
5336 int err_code = 0;
5337
5338 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5339 err_code |= ERR_WARN;
5340
5341 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5342 goto out;
5343
5344 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5345 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5346 file, line);
5347 err_code |= ERR_ALERT | ERR_FATAL;
5348 goto out;
5349 }
5350
5351 curpx->options2 &= ~PR_O2_CHK_ANY;
5352 curpx->options2 |= PR_O2_TCPCHK_CHK;
5353
5354 free_tcpcheck_vars(&rules->preset_vars);
5355 rules->list = NULL;
5356 rules->flags = 0;
5357
5358 cur_arg += 2;
5359 if (*args[cur_arg] && *args[cur_arg+1] &&
5360 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5361 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5362 if (cmd)
5363 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5364 }
5365 else {
5366 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5367 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5368 cmd = strdup("HELO localhost");
5369 }
5370
5371 var = tcpcheck_var_create("check.smtp_cmd");
5372 if (cmd == NULL || var == NULL) {
5373 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5374 goto error;
5375 }
5376 var->data.type = SMP_T_STR;
5377 var->data.u.str.area = cmd;
5378 var->data.u.str.data = strlen(cmd);
5379 LIST_INIT(&var->list);
5380 LIST_ADDQ(&rules->preset_vars, &var->list);
5381 cmd = NULL;
5382 var = NULL;
5383
5384 rs = tcpcheck_ruleset_lookup("*smtp-check");
5385 if (rs)
5386 goto ruleset_found;
5387
5388 rs = tcpcheck_ruleset_create("*smtp-check");
5389 if (rs == NULL) {
5390 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5391 goto error;
5392 }
5393
5394 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5395 1, curpx, &rs->rules, file, line, &errmsg);
5396 if (!chk) {
5397 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5398 goto error;
5399 }
5400 chk->index = 0;
5401 LIST_ADDQ(&rs->rules, &chk->list);
5402
5403 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5404 "min-recv", "4",
5405 "error-status", "L7RSP",
5406 "on-error", "%[check.payload(),cut_crlf]",
5407 ""},
5408 1, curpx, &rs->rules, file, line, &errmsg);
5409 if (!chk) {
5410 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5411 goto error;
5412 }
5413 chk->index = 1;
5414 LIST_ADDQ(&rs->rules, &chk->list);
5415
5416 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5417 "min-recv", "4",
5418 "error-status", "L7STS",
5419 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5420 "status-code", "check.payload(0,3)",
5421 ""},
5422 1, curpx, &rs->rules, file, line, &errmsg);
5423 if (!chk) {
5424 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5425 goto error;
5426 }
5427 chk->index = 2;
5428 LIST_ADDQ(&rs->rules, &chk->list);
5429
5430 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5431 1, curpx, &rs->rules, file, line, &errmsg);
5432 if (!chk) {
5433 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5434 goto error;
5435 }
5436 chk->index = 3;
5437 LIST_ADDQ(&rs->rules, &chk->list);
5438
5439 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5440 "min-recv", "4",
5441 "error-status", "L7STS",
5442 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5443 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5444 "status-code", "check.payload(0,3)",
5445 ""},
5446 1, curpx, &rs->rules, file, line, &errmsg);
5447 if (!chk) {
5448 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5449 goto error;
5450 }
5451 chk->index = 4;
5452 LIST_ADDQ(&rs->rules, &chk->list);
5453
5454 LIST_ADDQ(&tcpchecks_list, &rs->list);
5455
5456 ruleset_found:
5457 rules->list = &rs->rules;
5458 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SMTP_CHK);
5459
5460 out:
5461 free(errmsg);
5462 return err_code;
5463
5464 error:
5465 free(cmd);
5466 free(var);
5467 free_tcpcheck_vars(&rules->preset_vars);
5468 tcpcheck_ruleset_release(rs);
5469 err_code |= ERR_ALERT | ERR_FATAL;
5470 goto out;
5471}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005472
Christopher Fauletce355072020-04-02 11:44:39 +02005473/* Parses the "option pgsql-check" proxy keyword */
5474int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5475 const char *file, int line)
5476{
5477 static char pgsql_req[] = {
5478 "%[var(check.plen),htonl,hex]" /* The packet length*/
5479 "00030000" /* the version 3.0 */
5480 "7573657200" /* "user" key */
5481 "%[var(check.username),hex]00" /* the username */
5482 "00"
5483 };
5484
5485 struct tcpcheck_ruleset *rs = NULL;
5486 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5487 struct tcpcheck_rule *chk;
5488 struct tcpcheck_var *var = NULL;
5489 char *user = NULL, *errmsg = NULL;
5490 size_t packetlen = 0;
5491 int err_code = 0;
5492
5493 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5494 err_code |= ERR_WARN;
5495
5496 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5497 goto out;
5498
5499 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5500 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5501 file, line);
5502 err_code |= ERR_ALERT | ERR_FATAL;
5503 goto out;
5504 }
5505
5506
5507 curpx->options2 &= ~PR_O2_CHK_ANY;
5508 curpx->options2 |= PR_O2_TCPCHK_CHK;
5509
5510 free_tcpcheck_vars(&rules->preset_vars);
5511 rules->list = NULL;
5512 rules->flags = 0;
5513
5514 cur_arg += 2;
5515 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5516 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5517 file, line, args[0], args[1]);
5518 goto error;
5519 }
5520 if (strcmp(args[cur_arg], "user") == 0) {
5521 packetlen = 15 + strlen(args[cur_arg+1]);
5522 user = strdup(args[cur_arg+1]);
5523
5524 var = tcpcheck_var_create("check.username");
5525 if (user == NULL || var == NULL) {
5526 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5527 goto error;
5528 }
5529 var->data.type = SMP_T_STR;
5530 var->data.u.str.area = user;
5531 var->data.u.str.data = strlen(user);
5532 LIST_INIT(&var->list);
5533 LIST_ADDQ(&rules->preset_vars, &var->list);
5534 user = NULL;
5535 var = NULL;
5536
5537 var = tcpcheck_var_create("check.plen");
5538 if (var == NULL) {
5539 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5540 goto error;
5541 }
5542 var->data.type = SMP_T_SINT;
5543 var->data.u.sint = packetlen;
5544 LIST_INIT(&var->list);
5545 LIST_ADDQ(&rules->preset_vars, &var->list);
5546 var = NULL;
5547 }
5548 else {
5549 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5550 file, line, args[0], args[1]);
5551 goto error;
5552 }
5553
5554 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5555 if (rs)
5556 goto ruleset_found;
5557
5558 rs = tcpcheck_ruleset_create("*pgsql-check");
5559 if (rs == NULL) {
5560 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5561 goto error;
5562 }
5563
5564 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5565 1, curpx, &rs->rules, file, line, &errmsg);
5566 if (!chk) {
5567 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5568 goto error;
5569 }
5570 chk->index = 0;
5571 LIST_ADDQ(&rs->rules, &chk->list);
5572
5573 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5574 1, curpx, &rs->rules, file, line, &errmsg);
5575 if (!chk) {
5576 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5577 goto error;
5578 }
5579 chk->index = 1;
5580 LIST_ADDQ(&rs->rules, &chk->list);
5581
5582 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5583 "min-recv", "5",
5584 "error-status", "L7RSP",
5585 "on-error", "%[check.payload(6,0)]",
5586 ""},
5587 1, curpx, &rs->rules, file, line, &errmsg);
5588 if (!chk) {
5589 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5590 goto error;
5591 }
5592 chk->index = 2;
5593 LIST_ADDQ(&rs->rules, &chk->list);
5594
5595 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5596 "min-recv", "9",
5597 "error-status", "L7STS",
5598 "on-success", "PostgreSQL server is ok",
5599 "on-error", "PostgreSQL unknown error",
5600 ""},
5601 1, curpx, &rs->rules, file, line, &errmsg);
5602 if (!chk) {
5603 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5604 goto error;
5605 }
5606 chk->index = 3;
5607 LIST_ADDQ(&rs->rules, &chk->list);
5608
5609 LIST_ADDQ(&tcpchecks_list, &rs->list);
5610
5611 ruleset_found:
5612 rules->list = &rs->rules;
5613 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK);
5614
5615 out:
5616 free(errmsg);
5617 return err_code;
5618
5619 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005620 free(user);
5621 free(var);
5622 free_tcpcheck_vars(&rules->preset_vars);
5623 tcpcheck_ruleset_release(rs);
5624 err_code |= ERR_ALERT | ERR_FATAL;
5625 goto out;
5626}
5627
5628
5629/* Parses the "option mysql-check" proxy keyword */
5630int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5631 const char *file, int line)
5632{
5633 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5634 * const char mysql40_client_auth_pkt[] = {
5635 * "\x0e\x00\x00" // packet length
5636 * "\x01" // packet number
5637 * "\x00\x00" // client capabilities
5638 * "\x00\x00\x01" // max packet
5639 * "haproxy\x00" // username (null terminated string)
5640 * "\x00" // filler (always 0x00)
5641 * "\x01\x00\x00" // packet length
5642 * "\x00" // packet number
5643 * "\x01" // COM_QUIT command
5644 * };
5645 */
5646 static char mysql40_rsname[] = "*mysql40-check";
5647 static char mysql40_req[] = {
5648 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5649 "0080" /* client capabilities */
5650 "000001" /* max packet */
5651 "%[var(check.username),hex]00" /* the username */
5652 "00" /* filler (always 0x00) */
5653 "010000" /* packet length*/
5654 "00" /* sequence ID */
5655 "01" /* COM_QUIT command */
5656 };
5657
5658 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5659 * const char mysql41_client_auth_pkt[] = {
5660 * "\x0e\x00\x00\" // packet length
5661 * "\x01" // packet number
5662 * "\x00\x00\x00\x00" // client capabilities
5663 * "\x00\x00\x00\x01" // max packet
5664 * "\x21" // character set (UTF-8)
5665 * char[23] // All zeroes
5666 * "haproxy\x00" // username (null terminated string)
5667 * "\x00" // filler (always 0x00)
5668 * "\x01\x00\x00" // packet length
5669 * "\x00" // packet number
5670 * "\x01" // COM_QUIT command
5671 * };
5672 */
5673 static char mysql41_rsname[] = "*mysql41-check";
5674 static char mysql41_req[] = {
5675 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5676 "00820000" /* client capabilities */
5677 "00800001" /* max packet */
5678 "21" /* character set (UTF-8) */
5679 "000000000000000000000000" /* 23 bytes, al zeroes */
5680 "0000000000000000000000"
5681 "%[var(check.username),hex]00" /* the username */
5682 "00" /* filler (always 0x00) */
5683 "010000" /* packet length*/
5684 "00" /* sequence ID */
5685 "01" /* COM_QUIT command */
5686 };
5687
5688 struct tcpcheck_ruleset *rs = NULL;
5689 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5690 struct tcpcheck_rule *chk;
5691 struct tcpcheck_var *var = NULL;
5692 char *mysql_rsname = "*mysql-check";
5693 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5694 int index = 0, err_code = 0;
5695
5696 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5697 err_code |= ERR_WARN;
5698
5699 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5700 goto out;
5701
5702 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5703 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5704 file, line);
5705 err_code |= ERR_ALERT | ERR_FATAL;
5706 goto out;
5707 }
5708
5709 curpx->options2 &= ~PR_O2_CHK_ANY;
5710 curpx->options2 |= PR_O2_TCPCHK_CHK;
5711
5712 free_tcpcheck_vars(&rules->preset_vars);
5713 rules->list = NULL;
5714 rules->flags = 0;
5715
5716 cur_arg += 2;
5717 if (*args[cur_arg]) {
5718 char *user;
5719 int packetlen, userlen;
5720
5721 if (strcmp(args[cur_arg], "user") != 0) {
5722 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5723 file, line, args[0], args[1], args[cur_arg]);
5724 goto error;
5725 }
5726
5727 if (*(args[cur_arg+1]) == 0) {
5728 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5729 file, line, args[0], args[1], args[cur_arg]);
5730 goto error;
5731 }
5732
5733 hdr = calloc(4, sizeof(*hdr));
5734 user = strdup(args[cur_arg+1]);
5735 userlen = strlen(args[cur_arg+1]);
5736
5737 if (hdr == NULL || user == NULL) {
5738 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5739 goto error;
5740 }
5741
5742 if (*args[cur_arg+2]) {
5743 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5744 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5745 file, line, args[cur_arg], args[cur_arg+2]);
5746 goto error;
5747 }
5748 packetlen = userlen + 7 + 27;
5749 mysql_req = mysql41_req;
5750 mysql_rsname = mysql41_rsname;
5751 }
5752 else {
5753 packetlen = userlen + 7;
5754 mysql_req = mysql40_req;
5755 mysql_rsname = mysql40_rsname;
5756 }
5757
5758 hdr[0] = (unsigned char)(packetlen & 0xff);
5759 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
5760 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
5761 hdr[3] = 1;
5762
5763 var = tcpcheck_var_create("check.header");
5764 if (var == NULL) {
5765 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5766 goto error;
5767 }
5768 var->data.type = SMP_T_STR;
5769 var->data.u.str.area = hdr;
5770 var->data.u.str.data = 4;
5771 LIST_INIT(&var->list);
5772 LIST_ADDQ(&rules->preset_vars, &var->list);
5773 hdr = NULL;
5774 var = NULL;
5775
5776 var = tcpcheck_var_create("check.username");
5777 if (var == NULL) {
5778 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5779 goto error;
5780 }
5781 var->data.type = SMP_T_STR;
5782 var->data.u.str.area = user;
5783 var->data.u.str.data = strlen(user);
5784 LIST_INIT(&var->list);
5785 LIST_ADDQ(&rules->preset_vars, &var->list);
5786 user = NULL;
5787 var = NULL;
5788 }
5789
5790 rs = tcpcheck_ruleset_lookup(mysql_rsname);
5791 if (rs)
5792 goto ruleset_found;
5793
5794 rs = tcpcheck_ruleset_create(mysql_rsname);
5795 if (rs == NULL) {
5796 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5797 goto error;
5798 }
5799
5800 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5801 1, curpx, &rs->rules, file, line, &errmsg);
5802 if (!chk) {
5803 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5804 goto error;
5805 }
5806 chk->index = index++;
5807 LIST_ADDQ(&rs->rules, &chk->list);
5808
5809 if (mysql_req) {
5810 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
5811 1, curpx, &rs->rules, file, line, &errmsg);
5812 if (!chk) {
5813 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5814 goto error;
5815 }
5816 chk->index = index++;
5817 LIST_ADDQ(&rs->rules, &chk->list);
5818 }
5819
5820 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5821 1, curpx, &rs->rules, file, line, &errmsg);
5822 if (!chk) {
5823 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5824 goto error;
5825 }
5826 chk->expect.custom = tcpcheck_mysql_expect_iniths;
5827 chk->index = index++;
5828 LIST_ADDQ(&rs->rules, &chk->list);
5829
5830 if (mysql_req) {
5831 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5832 1, curpx, &rs->rules, file, line, &errmsg);
5833 if (!chk) {
5834 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5835 goto error;
5836 }
5837 chk->expect.custom = tcpcheck_mysql_expect_ok;
5838 chk->index = index++;
5839 LIST_ADDQ(&rs->rules, &chk->list);
5840 }
5841
5842 LIST_ADDQ(&tcpchecks_list, &rs->list);
5843
5844 ruleset_found:
5845 rules->list = &rs->rules;
5846 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_MYSQL_CHK);
5847
5848 out:
5849 free(errmsg);
5850 return err_code;
5851
5852 error:
5853 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02005854 free(user);
5855 free(var);
5856 free_tcpcheck_vars(&rules->preset_vars);
5857 tcpcheck_ruleset_release(rs);
5858 err_code |= ERR_ALERT | ERR_FATAL;
5859 goto out;
5860}
5861
Christopher Faulet1997eca2020-04-03 23:13:50 +02005862int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5863 const char *file, int line)
5864{
5865 static char *ldap_req = "300C020101600702010304008000";
5866
5867 struct tcpcheck_ruleset *rs = NULL;
5868 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5869 struct tcpcheck_rule *chk;
5870 char *errmsg = NULL;
5871 int err_code = 0;
5872
5873 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5874 err_code |= ERR_WARN;
5875
5876 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5877 goto out;
5878
5879 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5880 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5881 file, line);
5882 err_code |= ERR_ALERT | ERR_FATAL;
5883 goto out;
5884 }
5885
5886 curpx->options2 &= ~PR_O2_CHK_ANY;
5887 curpx->options2 |= PR_O2_TCPCHK_CHK;
5888
5889 free_tcpcheck_vars(&rules->preset_vars);
5890 rules->list = NULL;
5891 rules->flags = 0;
5892
5893 rs = tcpcheck_ruleset_lookup("*ldap-check");
5894 if (rs)
5895 goto ruleset_found;
5896
5897 rs = tcpcheck_ruleset_create("*ldap-check");
5898 if (rs == NULL) {
5899 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5900 goto error;
5901 }
5902
5903 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
5904 1, curpx, &rs->rules, file, line, &errmsg);
5905 if (!chk) {
5906 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5907 goto error;
5908 }
5909 chk->index = 0;
5910 LIST_ADDQ(&rs->rules, &chk->list);
5911
5912 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
5913 "min-recv", "14",
5914 "on-error", "Not LDAPv3 protocol",
5915 ""},
5916 1, curpx, &rs->rules, file, line, &errmsg);
5917 if (!chk) {
5918 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5919 goto error;
5920 }
5921 chk->index = 1;
5922 LIST_ADDQ(&rs->rules, &chk->list);
5923
5924 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5925 1, curpx, &rs->rules, file, line, &errmsg);
5926 if (!chk) {
5927 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5928 goto error;
5929 }
5930 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
5931 chk->index = 2;
5932 LIST_ADDQ(&rs->rules, &chk->list);
5933
5934 LIST_ADDQ(&tcpchecks_list, &rs->list);
5935
5936 ruleset_found:
5937 rules->list = &rs->rules;
5938 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_LDAP_CHK);
5939
5940 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02005941 free(errmsg);
5942 return err_code;
5943
5944 error:
5945 tcpcheck_ruleset_release(rs);
5946 err_code |= ERR_ALERT | ERR_FATAL;
5947 goto out;
5948}
5949
5950int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5951 const char *file, int line)
5952{
5953 struct tcpcheck_ruleset *rs = NULL;
5954 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5955 struct tcpcheck_rule *chk;
5956 char *spop_req = NULL;
5957 char *errmsg = NULL;
5958 int spop_len = 0, err_code = 0;
5959
5960 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5961 err_code |= ERR_WARN;
5962
5963 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5964 goto out;
5965
5966 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5967 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5968 file, line);
5969 err_code |= ERR_ALERT | ERR_FATAL;
5970 goto out;
5971 }
5972
5973 curpx->options2 &= ~PR_O2_CHK_ANY;
5974 curpx->options2 |= PR_O2_TCPCHK_CHK;
5975
5976 free_tcpcheck_vars(&rules->preset_vars);
5977 rules->list = NULL;
5978 rules->flags = 0;
5979
5980
5981 rs = tcpcheck_ruleset_lookup("*spop-check");
5982 if (rs)
5983 goto ruleset_found;
5984
5985 rs = tcpcheck_ruleset_create("*spop-check");
5986 if (rs == NULL) {
5987 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5988 goto error;
5989 }
5990
5991 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
5992 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5993 goto error;
5994 }
5995 chunk_reset(&trash);
5996 dump_binary(&trash, spop_req, spop_len);
5997 trash.area[trash.data] = '\0';
5998
5999 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6000 1, curpx, &rs->rules, file, line, &errmsg);
6001 if (!chk) {
6002 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6003 goto error;
6004 }
6005 chk->index = 0;
6006 LIST_ADDQ(&rs->rules, &chk->list);
6007
6008 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
6009 1, curpx, &rs->rules, file, line, &errmsg);
6010 if (!chk) {
6011 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6012 goto error;
6013 }
6014 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6015 chk->index = 1;
6016 LIST_ADDQ(&rs->rules, &chk->list);
6017
6018 LIST_ADDQ(&tcpchecks_list, &rs->list);
6019
6020 ruleset_found:
6021 rules->list = &rs->rules;
6022 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SPOP_CHK);
6023
6024 out:
6025 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006026 free(errmsg);
6027 return err_code;
6028
6029 error:
6030 tcpcheck_ruleset_release(rs);
6031 err_code |= ERR_ALERT | ERR_FATAL;
6032 goto out;
6033}
Christopher Fauletce355072020-04-02 11:44:39 +02006034
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006035
Christopher Fauletce8111e2020-04-06 15:04:11 +02006036/* Parse the "addr" server keyword */
6037static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6038 char **errmsg)
6039{
6040 struct sockaddr_storage *sk;
6041 struct protocol *proto;
6042 int port1, port2, err_code = 0;
6043
6044
6045 if (!*args[*cur_arg+1]) {
6046 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6047 goto error;
6048 }
6049
6050 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6051 if (!sk) {
6052 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6053 goto error;
6054 }
6055
6056 proto = protocol_by_family(sk->ss_family);
6057 if (!proto || !proto->connect) {
6058 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6059 args[*cur_arg], args[*cur_arg+1]);
6060 goto error;
6061 }
6062
6063 if (port1 != port2) {
6064 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6065 args[*cur_arg], args[*cur_arg+1]);
6066 goto error;
6067 }
6068
6069 srv->check.addr = srv->agent.addr = *sk;
6070 srv->flags |= SRV_F_CHECKADDR;
6071 srv->flags |= SRV_F_AGENTADDR;
6072
6073 out:
6074 return err_code;
6075
6076 error:
6077 err_code |= ERR_ALERT | ERR_FATAL;
6078 goto out;
6079}
6080
6081
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006082/* Parse the "agent-addr" server keyword */
6083static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6084 char **errmsg)
6085{
6086 int err_code = 0;
6087
6088 if (!*(args[*cur_arg+1])) {
6089 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6090 goto error;
6091 }
6092 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6093 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6094 goto error;
6095 }
6096
6097 out:
6098 return err_code;
6099
6100 error:
6101 err_code |= ERR_ALERT | ERR_FATAL;
6102 goto out;
6103}
6104
6105/* Parse the "agent-check" server keyword */
6106static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6107 char **errmsg)
6108{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006109 struct tcpcheck_ruleset *rs = NULL;
6110 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6111 struct tcpcheck_rule *chk;
6112 int err_code = 0;
6113
6114 if (srv->do_agent)
6115 goto out;
6116
6117 if (!rules) {
6118 rules = calloc(1, sizeof(*rules));
6119 if (!rules) {
6120 memprintf(errmsg, "out of memory.");
6121 goto error;
6122 }
6123 LIST_INIT(&rules->preset_vars);
6124 srv->agent.tcpcheck_rules = rules;
6125 }
6126 rules->list = NULL;
6127 rules->flags = 0;
6128
6129 rs = tcpcheck_ruleset_lookup("*agent-check");
6130 if (rs)
6131 goto ruleset_found;
6132
6133 rs = tcpcheck_ruleset_create("*agent-check");
6134 if (rs == NULL) {
6135 memprintf(errmsg, "out of memory.");
6136 goto error;
6137 }
6138
6139 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6140 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6141 if (!chk) {
6142 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6143 goto error;
6144 }
6145 chk->index = 0;
6146 LIST_ADDQ(&rs->rules, &chk->list);
6147
6148 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6149 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6150 if (!chk) {
6151 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6152 goto error;
6153 }
6154 chk->expect.custom = tcpcheck_agent_expect_reply;
6155 chk->index = 1;
6156 LIST_ADDQ(&rs->rules, &chk->list);
6157
6158 LIST_ADDQ(&tcpchecks_list, &rs->list);
6159
6160 ruleset_found:
6161 rules->list = &rs->rules;
6162 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_AGENT_CHK);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006163 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006164
6165 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006166 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006167
6168 error:
6169 deinit_srv_agent_check(srv);
6170 tcpcheck_ruleset_release(rs);
6171 err_code |= ERR_ALERT | ERR_FATAL;
6172 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006173}
6174
6175/* Parse the "agent-inter" server keyword */
6176static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6177 char **errmsg)
6178{
6179 const char *err = NULL;
6180 unsigned int delay;
6181 int err_code = 0;
6182
6183 if (!*(args[*cur_arg+1])) {
6184 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6185 goto error;
6186 }
6187
6188 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6189 if (err == PARSE_TIME_OVER) {
6190 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6191 args[*cur_arg+1], args[*cur_arg], srv->id);
6192 goto error;
6193 }
6194 else if (err == PARSE_TIME_UNDER) {
6195 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6196 args[*cur_arg+1], args[*cur_arg], srv->id);
6197 goto error;
6198 }
6199 else if (err) {
6200 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6201 *err, srv->id);
6202 goto error;
6203 }
6204 if (delay <= 0) {
6205 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6206 delay, args[*cur_arg], srv->id);
6207 goto error;
6208 }
6209 srv->agent.inter = delay;
6210
6211 out:
6212 return err_code;
6213
6214 error:
6215 err_code |= ERR_ALERT | ERR_FATAL;
6216 goto out;
6217}
6218
6219/* Parse the "agent-port" server keyword */
6220static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6221 char **errmsg)
6222{
6223 int err_code = 0;
6224
6225 if (!*(args[*cur_arg+1])) {
6226 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6227 goto error;
6228 }
6229
6230 global.maxsock++;
6231 srv->agent.port = atol(args[*cur_arg+1]);
6232
6233 out:
6234 return err_code;
6235
6236 error:
6237 err_code |= ERR_ALERT | ERR_FATAL;
6238 goto out;
6239}
6240
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006241int set_srv_agent_send(struct server *srv, const char *send)
6242{
6243 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6244 struct tcpcheck_var *var = NULL;
6245 char *str;
6246
6247 str = strdup(send);
6248 var = tcpcheck_var_create("check.agent_string");
6249 if (str == NULL || var == NULL)
6250 goto error;
6251
6252 free_tcpcheck_vars(&rules->preset_vars);
6253
6254 var->data.type = SMP_T_STR;
6255 var->data.u.str.area = str;
6256 var->data.u.str.data = strlen(str);
6257 LIST_INIT(&var->list);
6258 LIST_ADDQ(&rules->preset_vars, &var->list);
6259
6260 return 1;
6261
6262 error:
6263 free(str);
6264 free(var);
6265 return 0;
6266}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006267
6268/* Parse the "agent-send" server keyword */
6269static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6270 char **errmsg)
6271{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006272 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006273 int err_code = 0;
6274
6275 if (!*(args[*cur_arg+1])) {
6276 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6277 goto error;
6278 }
6279
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006280 if (!rules) {
6281 rules = calloc(1, sizeof(*rules));
6282 if (!rules) {
6283 memprintf(errmsg, "out of memory.");
6284 goto error;
6285 }
6286 LIST_INIT(&rules->preset_vars);
6287 srv->agent.tcpcheck_rules = rules;
6288 }
6289
6290 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006291 memprintf(errmsg, "out of memory.");
6292 goto error;
6293 }
6294
6295 out:
6296 return err_code;
6297
6298 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006299 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006300 err_code |= ERR_ALERT | ERR_FATAL;
6301 goto out;
6302}
6303
6304/* Parse the "no-agent-send" server keyword */
6305static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6306 char **errmsg)
6307{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006308 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006309 return 0;
6310}
6311
Christopher Fauletce8111e2020-04-06 15:04:11 +02006312/* Parse the "check" server keyword */
6313static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6314 char **errmsg)
6315{
6316 srv->do_check = 1;
6317 return 0;
6318}
6319
6320/* Parse the "check-send-proxy" server keyword */
6321static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6322 char **errmsg)
6323{
6324 srv->check.send_proxy = 1;
6325 return 0;
6326}
6327
6328/* Parse the "check-via-socks4" server keyword */
6329static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6330 char **errmsg)
6331{
6332 srv->check.via_socks4 = 1;
6333 return 0;
6334}
6335
6336/* Parse the "no-check" server keyword */
6337static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6338 char **errmsg)
6339{
6340 deinit_srv_check(srv);
6341 return 0;
6342}
6343
6344/* Parse the "no-check-send-proxy" server keyword */
6345static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6346 char **errmsg)
6347{
6348 srv->check.send_proxy = 0;
6349 return 0;
6350}
6351
6352/* Parse the "rise" server keyword */
6353static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6354 char **errmsg)
6355{
6356 int err_code = 0;
6357
6358 if (!*args[*cur_arg + 1]) {
6359 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6360 goto error;
6361 }
6362
6363 srv->check.rise = atol(args[*cur_arg+1]);
6364 if (srv->check.rise <= 0) {
6365 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6366 goto error;
6367 }
6368
6369 if (srv->check.health)
6370 srv->check.health = srv->check.rise;
6371
6372 out:
6373 return err_code;
6374
6375 error:
6376 deinit_srv_agent_check(srv);
6377 err_code |= ERR_ALERT | ERR_FATAL;
6378 goto out;
6379 return 0;
6380}
6381
6382/* Parse the "fall" server keyword */
6383static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6384 char **errmsg)
6385{
6386 int err_code = 0;
6387
6388 if (!*args[*cur_arg + 1]) {
6389 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6390 goto error;
6391 }
6392
6393 srv->check.fall = atol(args[*cur_arg+1]);
6394 if (srv->check.fall <= 0) {
6395 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6396 goto error;
6397 }
6398
6399 out:
6400 return err_code;
6401
6402 error:
6403 deinit_srv_agent_check(srv);
6404 err_code |= ERR_ALERT | ERR_FATAL;
6405 goto out;
6406 return 0;
6407}
6408
6409/* Parse the "inter" server keyword */
6410static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6411 char **errmsg)
6412{
6413 const char *err = NULL;
6414 unsigned int delay;
6415 int err_code = 0;
6416
6417 if (!*(args[*cur_arg+1])) {
6418 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6419 goto error;
6420 }
6421
6422 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6423 if (err == PARSE_TIME_OVER) {
6424 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6425 args[*cur_arg+1], args[*cur_arg], srv->id);
6426 goto error;
6427 }
6428 else if (err == PARSE_TIME_UNDER) {
6429 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6430 args[*cur_arg+1], args[*cur_arg], srv->id);
6431 goto error;
6432 }
6433 else if (err) {
6434 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6435 *err, srv->id);
6436 goto error;
6437 }
6438 if (delay <= 0) {
6439 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6440 delay, args[*cur_arg], srv->id);
6441 goto error;
6442 }
6443 srv->check.inter = delay;
6444
6445 out:
6446 return err_code;
6447
6448 error:
6449 err_code |= ERR_ALERT | ERR_FATAL;
6450 goto out;
6451}
6452
6453
6454/* Parse the "fastinter" server keyword */
6455static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6456 char **errmsg)
6457{
6458 const char *err = NULL;
6459 unsigned int delay;
6460 int err_code = 0;
6461
6462 if (!*(args[*cur_arg+1])) {
6463 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6464 goto error;
6465 }
6466
6467 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6468 if (err == PARSE_TIME_OVER) {
6469 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6470 args[*cur_arg+1], args[*cur_arg], srv->id);
6471 goto error;
6472 }
6473 else if (err == PARSE_TIME_UNDER) {
6474 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6475 args[*cur_arg+1], args[*cur_arg], srv->id);
6476 goto error;
6477 }
6478 else if (err) {
6479 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6480 *err, srv->id);
6481 goto error;
6482 }
6483 if (delay <= 0) {
6484 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6485 delay, args[*cur_arg], srv->id);
6486 goto error;
6487 }
6488 srv->check.fastinter = delay;
6489
6490 out:
6491 return err_code;
6492
6493 error:
6494 err_code |= ERR_ALERT | ERR_FATAL;
6495 goto out;
6496}
6497
6498
6499/* Parse the "downinter" server keyword */
6500static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6501 char **errmsg)
6502{
6503 const char *err = NULL;
6504 unsigned int delay;
6505 int err_code = 0;
6506
6507 if (!*(args[*cur_arg+1])) {
6508 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6509 goto error;
6510 }
6511
6512 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6513 if (err == PARSE_TIME_OVER) {
6514 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6515 args[*cur_arg+1], args[*cur_arg], srv->id);
6516 goto error;
6517 }
6518 else if (err == PARSE_TIME_UNDER) {
6519 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6520 args[*cur_arg+1], args[*cur_arg], srv->id);
6521 goto error;
6522 }
6523 else if (err) {
6524 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6525 *err, srv->id);
6526 goto error;
6527 }
6528 if (delay <= 0) {
6529 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6530 delay, args[*cur_arg], srv->id);
6531 goto error;
6532 }
6533 srv->check.downinter = delay;
6534
6535 out:
6536 return err_code;
6537
6538 error:
6539 err_code |= ERR_ALERT | ERR_FATAL;
6540 goto out;
6541}
6542
6543/* Parse the "port" server keyword */
6544static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6545 char **errmsg)
6546{
6547 int err_code = 0;
6548
6549 if (!*(args[*cur_arg+1])) {
6550 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6551 goto error;
6552 }
6553
6554 global.maxsock++;
6555 srv->check.port = atol(args[*cur_arg+1]);
6556 srv->flags |= SRV_F_CHECKPORT;
6557
6558 out:
6559 return err_code;
6560
6561 error:
6562 err_code |= ERR_ALERT | ERR_FATAL;
6563 goto out;
6564}
6565
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006566static struct cfg_kw_list cfg_kws = {ILH, {
6567 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6568 { 0, NULL, NULL },
6569}};
6570
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006571static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006572 { "addr", srv_parse_addr, 1, 1 }, /* IP address to send health to or to probe from agent-check */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006573 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6574 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6575 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6576 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6577 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006578 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6579 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6580 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006581 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006582 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6583 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6584 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6585 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6586 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6587 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6588 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6589 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006590 { NULL, NULL, 0 },
6591}};
6592
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006593INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006594INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006595
Willy Tarreaubd741542010-03-16 18:46:54 +01006596/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006597 * Local variables:
6598 * c-indent-level: 8
6599 * c-basic-offset: 8
6600 * End:
6601 */