blob: e5a7ecf0cf0dd946bcb1d445f9d1e741906a3ee4 [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>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
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
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001205 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001659 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1660 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001686 * addr if specified on the server. otherwise, use the server addr (it
1687 * MUST exist at this step).
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001688 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001689 *conn->dst = (is_addr(&connect->addr)
1690 ? connect->addr
1691 : (is_addr(&check->addr) ? check->addr : s->addr));
1692 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001693
Christopher Faulet61cc8522020-04-20 14:54:42 +02001694 port = 0;
1695 if (!port && connect->port)
1696 port = connect->port;
1697 if (!port && connect->port_expr) {
1698 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001699
Christopher Faulet61cc8522020-04-20 14:54:42 +02001700 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1701 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1702 connect->port_expr, SMP_T_SINT);
1703 if (smp)
1704 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001705 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001706 if (!port && is_inet_addr(&connect->addr))
1707 port = get_host_port(&connect->addr);
1708 if (!port && check->port)
1709 port = check->port;
1710 if (!port && is_inet_addr(&check->addr))
1711 port = get_host_port(&check->addr);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001712 if (!port) {
1713 /* The server MUST exist here */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001714 port = s->svc_port;
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001715 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001716 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001717
Christopher Faulet61cc8522020-04-20 14:54:42 +02001718 xprt = ((connect->options & TCPCHK_OPT_SSL)
1719 ? xprt_get(XPRT_SSL)
1720 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 conn_prepare(conn, proto, xprt);
1723 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001724
Christopher Faulet61cc8522020-04-20 14:54:42 +02001725 status = SF_ERR_INTERNAL;
1726 if (proto && proto->connect) {
1727 struct tcpcheck_rule *next;
1728 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1731 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001732
Christopher Faulet61cc8522020-04-20 14:54:42 +02001733 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1734 if (!next || next->action != TCPCHK_ACT_EXPECT)
1735 flags |= CONNECT_DELACK_ALWAYS;
1736 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001737 }
1738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 if (status != SF_ERR_NONE)
1740 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 conn->flags |= CO_FL_PRIVATE;
1743 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001744
Christopher Faulet61cc8522020-04-20 14:54:42 +02001745 /* The mux may be initialized now if there isn't server attached to the
1746 * check (email alerts) or if there is a mux proto specified or if there
1747 * is no alpn.
1748 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001749 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1750 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001751 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001752
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 if (connect->mux_proto)
1754 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001755 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001756 mux_ops = check->mux_proto->mux;
1757 else {
1758 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1759 ? PROTO_MODE_HTTP
1760 : PROTO_MODE_TCP);
1761
1762 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001763 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001764 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1765 status = SF_ERR_INTERNAL;
1766 goto fail_check;
1767 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001768 }
1769
Christopher Faulet61cc8522020-04-20 14:54:42 +02001770#ifdef USE_OPENSSL
1771 if (connect->sni)
1772 ssl_sock_set_servername(conn, connect->sni);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001773 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001774 ssl_sock_set_servername(conn, s->check.sni);
1775
1776 if (connect->alpn)
1777 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001778 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001779 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1780#endif
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001781 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001782 conn->send_proxy_ofs = 1;
1783 conn->flags |= CO_FL_SOCKS4;
1784 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001785 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001786 conn->send_proxy_ofs = 1;
1787 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001788 }
1789
Christopher Faulet61cc8522020-04-20 14:54:42 +02001790 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1791 conn->send_proxy_ofs = 1;
1792 conn->flags |= CO_FL_SEND_PROXY;
1793 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001794 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001795 conn->send_proxy_ofs = 1;
1796 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001797 }
1798
Christopher Faulet61cc8522020-04-20 14:54:42 +02001799 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1800 /* Some servers don't like reset on close */
1801 fdtab[cs->conn->handle.fd].linger_risk = 0;
1802 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001803
Christopher Faulet61cc8522020-04-20 14:54:42 +02001804 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1805 if (xprt_add_hs(conn) < 0)
1806 status = SF_ERR_RESOURCE;
1807 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001808
Christopher Faulet61cc8522020-04-20 14:54:42 +02001809 fail_check:
1810 /* It can return one of :
1811 * - SF_ERR_NONE if everything's OK
1812 * - SF_ERR_SRVTO if there are no more servers
1813 * - SF_ERR_SRVCL if the connection was refused by the server
1814 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1815 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1816 * - SF_ERR_INTERNAL for any other purely internal errors
1817 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1818 * Note that we try to prevent the network stack from sending the ACK during the
1819 * connect() when a pure TCP check is used (without PROXY protocol).
1820 */
1821 switch (status) {
1822 case SF_ERR_NONE:
1823 /* we allow up to min(inter, timeout.connect) for a connection
1824 * to establish but only when timeout.check is set as it may be
1825 * to short for a full check otherwise
1826 */
1827 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001828
Christopher Faulet61cc8522020-04-20 14:54:42 +02001829 if (proxy->timeout.check && proxy->timeout.connect) {
1830 int t_con = tick_add(now_ms, proxy->timeout.connect);
1831 t->expire = tick_first(t->expire, t_con);
1832 }
1833 break;
1834 case SF_ERR_SRVTO: /* ETIMEDOUT */
1835 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1836 case SF_ERR_PRXCOND:
1837 case SF_ERR_RESOURCE:
1838 case SF_ERR_INTERNAL:
1839 chk_report_conn_err(check, errno, 0);
1840 ret = TCPCHK_EVAL_STOP;
1841 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001842 }
1843
Christopher Faulet61cc8522020-04-20 14:54:42 +02001844 /* don't do anything until the connection is established */
1845 if (conn->flags & CO_FL_WAIT_XPRT) {
1846 ret = TCPCHK_EVAL_WAIT;
1847 goto out;
1848 }
1849
1850 out:
1851 if (conn && check->result == CHK_RES_FAILED)
1852 conn->flags |= CO_FL_ERROR;
1853 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001854}
1855
Christopher Faulet61cc8522020-04-20 14:54:42 +02001856/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1857 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1858 * TCPCHK_EVAL_STOP if an error occurred.
1859 */
1860static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001861{
1862 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001863 struct tcpcheck_send *send = &rule->send;
1864 struct conn_stream *cs = check->cs;
1865 struct connection *conn = cs_conn(cs);
1866 struct buffer *tmp = NULL;
1867 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001868
Christopher Faulet61cc8522020-04-20 14:54:42 +02001869 /* reset the read & write buffer */
1870 b_reset(&check->bi);
1871 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001872
Christopher Faulet61cc8522020-04-20 14:54:42 +02001873 switch (send->type) {
1874 case TCPCHK_SEND_STRING:
1875 case TCPCHK_SEND_BINARY:
1876 if (istlen(send->data) >= b_size(&check->bo)) {
1877 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1878 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1879 tcpcheck_get_step_id(check, rule));
1880 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1881 ret = TCPCHK_EVAL_STOP;
1882 goto out;
1883 }
1884 b_putist(&check->bo, send->data);
1885 break;
1886 case TCPCHK_SEND_STRING_LF:
1887 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1888 if (!b_data(&check->bo))
1889 goto out;
1890 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001891 case TCPCHK_SEND_BINARY_LF: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02001892 int len = b_size(&check->bo);
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001893
Christopher Faulet61cc8522020-04-20 14:54:42 +02001894 tmp = alloc_trash_chunk();
1895 if (!tmp)
1896 goto error_lf;
1897 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1898 if (!b_data(tmp))
1899 goto out;
1900 tmp->area[tmp->data] = '\0';
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001901 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001902 goto error_lf;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001903 check->bo.data = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001904 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001905 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001906 case TCPCHK_SEND_HTTP: {
1907 struct htx_sl *sl;
1908 struct ist meth, uri, vsn, clen, body;
1909 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001910
Christopher Faulet61cc8522020-04-20 14:54:42 +02001911 tmp = alloc_trash_chunk();
1912 if (!tmp)
1913 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001914
Christopher Faulet61cc8522020-04-20 14:54:42 +02001915 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1916 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1917 : http_known_methods[send->http.meth.meth]);
1918 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1919 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001920
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001921 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1922 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001923 slflags |= HTX_SL_F_VER_11;
1924 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1925 if (!isttest(send->http.body))
1926 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001927
Christopher Faulet61cc8522020-04-20 14:54:42 +02001928 htx = htx_from_buf(&check->bo);
1929 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1930 if (!sl)
1931 goto error_htx;
1932 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001933
Christopher Faulet61cc8522020-04-20 14:54:42 +02001934 body = send->http.body; // TODO: handle body_fmt
1935 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001936
Christopher Faulet61cc8522020-04-20 14:54:42 +02001937 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1938 !htx_add_header(htx, ist("Content-length"), clen))
1939 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001940
Christopher Faulet61cc8522020-04-20 14:54:42 +02001941 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1942 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001943
Christopher Faulet61cc8522020-04-20 14:54:42 +02001944 list_for_each_entry(hdr, &send->http.hdrs, list) {
1945 chunk_reset(tmp);
1946 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1947 if (!b_data(tmp))
1948 continue;
1949 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1950 goto error_htx;
1951 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001952
Christopher Faulet61cc8522020-04-20 14:54:42 +02001953 }
1954 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1955 chunk_reset(tmp);
1956 httpchk_build_status_header(check->server, tmp);
1957 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1958 goto error_htx;
1959 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001960
Christopher Faulet61cc8522020-04-20 14:54:42 +02001961 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1962 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1963 !htx_add_endof(htx, HTX_BLK_EOM))
1964 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001965
Christopher Faulet61cc8522020-04-20 14:54:42 +02001966 htx_to_buf(htx, &check->bo);
1967 break;
1968 }
1969 case TCPCHK_SEND_UNDEF:
1970 /* Should never happen. */
1971 ret = TCPCHK_EVAL_STOP;
1972 goto out;
1973 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001974
Christopher Faulet6d471212020-04-22 11:09:25 +02001975
1976 if (conn->mux->snd_buf(cs, &check->bo,
1977 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001978 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001979 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001980 goto out;
1981 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001982 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001983 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001984 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1985 ret = TCPCHK_EVAL_WAIT;
1986 goto out;
1987 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001988
Christopher Faulet61cc8522020-04-20 14:54:42 +02001989 out:
1990 free_trash_chunk(tmp);
1991 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001992
Christopher Faulet61cc8522020-04-20 14:54:42 +02001993 error_htx:
1994 if (htx) {
1995 htx_reset(htx);
1996 htx_to_buf(htx, &check->bo);
1997 }
1998 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1999 tcpcheck_get_step_id(check, rule));
2000 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2001 ret = TCPCHK_EVAL_STOP;
2002 goto out;
2003
2004 error_lf:
2005 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2006 tcpcheck_get_step_id(check, rule));
2007 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2008 ret = TCPCHK_EVAL_STOP;
2009 goto out;
2010
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002011}
2012
Christopher Faulet61cc8522020-04-20 14:54:42 +02002013/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2014 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2015 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2016 * TCPCHK_EVAL_STOP if an error occurred.
2017 */
2018static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002019{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002020 struct conn_stream *cs = check->cs;
2021 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002022 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002023 size_t max, read, cur_read = 0;
2024 int is_empty;
2025 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002026
Christopher Faulet61cc8522020-04-20 14:54:42 +02002027 if (check->wait_list.events & SUB_RETRY_RECV)
2028 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002029
Christopher Faulet61cc8522020-04-20 14:54:42 +02002030 if (cs->flags & CS_FL_EOS)
2031 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002032
Christopher Faulet61cc8522020-04-20 14:54:42 +02002033 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002034
Christopher Faulet61cc8522020-04-20 14:54:42 +02002035 /* prepare to detect if the mux needs more room */
2036 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002037
Christopher Faulet61cc8522020-04-20 14:54:42 +02002038 while ((cs->flags & CS_FL_RCV_MORE) ||
2039 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2040 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2041 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2042 cur_read += read;
2043 if (!read ||
2044 (cs->flags & CS_FL_WANT_ROOM) ||
2045 (--read_poll <= 0) ||
2046 (read < max && read >= global.tune.recv_enough))
2047 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002048 }
2049
Christopher Faulet61cc8522020-04-20 14:54:42 +02002050 end_recv:
2051 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2052 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2053 /* Report network errors only if we got no other data. Otherwise
2054 * we'll let the upper layers decide whether the response is OK
2055 * or not. It is very common that an RST sent by the server is
2056 * reported as an error just after the last data chunk.
2057 */
2058 goto stop;
2059 }
2060 if (!cur_read) {
2061 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2062 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2063 goto wait_more_data;
2064 }
2065 if (is_empty) {
2066 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2067 tcpcheck_get_step_id(check, rule));
2068 if (rule->comment)
2069 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2070 set_server_check_status(check, rule->expect.err_status, trash.area);
2071 goto stop;
2072 }
2073 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002074
2075 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002076 return ret;
2077
Christopher Faulet61cc8522020-04-20 14:54:42 +02002078 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002079 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002080 goto out;
2081
2082 wait_more_data:
2083 ret = TCPCHK_EVAL_WAIT;
2084 goto out;
2085}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002086
Christopher Faulet61cc8522020-04-20 14:54:42 +02002087/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2088 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2089 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2090 * error occurred.
2091 */
2092static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
Christopher Faulet267b01b2020-04-04 10:27:09 +02002093{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002094 struct htx *htx = htxbuf(&check->bi);
2095 struct htx_sl *sl;
2096 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002097 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002099 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002100 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002101 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002102 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002103
Christopher Faulet61cc8522020-04-20 14:54:42 +02002104 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002105
Christopher Faulet61cc8522020-04-20 14:54:42 +02002106 if (htx->flags & HTX_FL_PARSING_ERROR) {
2107 status = HCHK_STATUS_L7RSP;
2108 goto error;
2109 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002110
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 if (htx_is_empty(htx)) {
2112 if (last_read) {
2113 status = HCHK_STATUS_L7RSP;
2114 goto error;
2115 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002116 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002117 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002118
Christopher Faulet61cc8522020-04-20 14:54:42 +02002119 sl = http_get_stline(htx);
2120 check->code = sl->info.res.status;
2121
2122 if (check->server &&
2123 (check->server->proxy->options & PR_O_DISABLE404) &&
2124 (check->server->next_state != SRV_ST_STOPPED) &&
2125 (check->code == 404)) {
2126 /* 404 may be accepted as "stopping" only if the server was up */
2127 goto out;
2128 }
2129
2130 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2131 /* Make GCC happy ; initialize match to a failure state. */
2132 match = inverse;
2133
2134 switch (expect->type) {
2135 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002136 match = 0;
2137 for (i = 0; i < expect->codes.num; i++) {
2138 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2139 sl->info.res.status <= expect->codes.codes[i][1]) {
2140 match = 1;
2141 break;
2142 }
2143 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002144
2145 /* Set status and description in case of error */
2146 status = HCHK_STATUS_L7STS;
2147 desc = htx_sl_res_reason(sl);
2148 break;
2149 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2150 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2151
2152 /* Set status and description in case of error */
2153 status = HCHK_STATUS_L7STS;
2154 desc = htx_sl_res_reason(sl);
2155 break;
2156
2157 case TCPCHK_EXPECT_HTTP_BODY:
2158 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2159 chunk_reset(&trash);
2160 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2161 enum htx_blk_type type = htx_get_blk_type(blk);
2162
2163 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2164 break;
2165 if (type == HTX_BLK_DATA) {
2166 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2167 break;
2168 }
2169 }
2170
2171 if (!b_data(&trash)) {
2172 if (!last_read)
2173 goto wait_more_data;
2174 status = HCHK_STATUS_L7RSP;
2175 desc = ist("HTTP content check could not find a response body");
2176 goto error;
2177 }
2178
2179 if (!last_read &&
2180 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2181 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2182 ret = TCPCHK_EVAL_WAIT;
2183 goto out;
2184 }
2185
2186 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002187 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002188 else
2189 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2190
2191 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002192 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002193 desc = (inverse
2194 ? ist("HTTP check matched unwanted content")
2195 : ist("HTTP content check did not match"));
2196 break;
2197
2198 default:
2199 /* should never happen */
2200 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002201 goto error;
2202 }
2203
Christopher Faulet61cc8522020-04-20 14:54:42 +02002204 /* Wait for more data on mismatch only if no minimum is defined (-1),
2205 * otherwise the absence of match is already conclusive.
2206 */
2207 if (!match && !last_read && (expect->min_recv == -1)) {
2208 ret = TCPCHK_EVAL_WAIT;
2209 goto out;
2210 }
2211
2212 if (!(match ^ inverse))
2213 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002214
2215 out:
2216 free_trash_chunk(msg);
2217 return ret;
2218
2219 error:
2220 ret = TCPCHK_EVAL_STOP;
2221 msg = alloc_trash_chunk();
2222 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002223 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002224 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2225 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002226
2227 wait_more_data:
2228 ret = TCPCHK_EVAL_WAIT;
2229 goto out;
2230}
2231
Christopher Faulet61cc8522020-04-20 14:54:42 +02002232/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2233 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2234 * if an error occurred.
2235 */
2236static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2237{
2238 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2239 struct tcpcheck_expect *expect = &rule->expect;
2240 struct buffer *msg = NULL;
2241 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002242
Christopher Faulet61cc8522020-04-20 14:54:42 +02002243 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002244
Christopher Faulet61cc8522020-04-20 14:54:42 +02002245 /* The current expect might need more data than the previous one, check again
2246 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002247 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002248 if (!last_read) {
2249 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2250 (b_data(&check->bi) < istlen(expect->data))) {
2251 ret = TCPCHK_EVAL_WAIT;
2252 goto out;
2253 }
2254 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2255 ret = TCPCHK_EVAL_WAIT;
2256 goto out;
2257 }
2258 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002259
Christopher Faulet61cc8522020-04-20 14:54:42 +02002260 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2261 /* Make GCC happy ; initialize match to a failure state. */
2262 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002263
Christopher Faulet61cc8522020-04-20 14:54:42 +02002264 switch (expect->type) {
2265 case TCPCHK_EXPECT_STRING:
2266 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002267 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002268 break;
2269 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002270 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002271 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002272
Christopher Faulet61cc8522020-04-20 14:54:42 +02002273 case TCPCHK_EXPECT_REGEX_BINARY:
2274 chunk_reset(&trash);
2275 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002276 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002277 break;
2278 case TCPCHK_EXPECT_CUSTOM:
2279 if (expect->custom)
2280 ret = expect->custom(check, rule, last_read);
2281 goto out;
2282 default:
2283 /* Should never happen. */
2284 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002285 goto out;
2286 }
2287
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002288
Christopher Faulet61cc8522020-04-20 14:54:42 +02002289 /* Wait for more data on mismatch only if no minimum is defined (-1),
2290 * otherwise the absence of match is already conclusive.
2291 */
2292 if (!match && !last_read && (expect->min_recv == -1)) {
2293 ret = TCPCHK_EVAL_WAIT;
2294 goto out;
2295 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002296
Christopher Faulet61cc8522020-04-20 14:54:42 +02002297 /* Result as expected, next rule. */
2298 if (match ^ inverse)
2299 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002300
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002301
Christopher Faulet61cc8522020-04-20 14:54:42 +02002302 /* From this point on, we matched something we did not want, this is an error state. */
2303 ret = TCPCHK_EVAL_STOP;
2304 msg = alloc_trash_chunk();
2305 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002306 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002307 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2308 free_trash_chunk(msg);
2309 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002310
Christopher Faulet61cc8522020-04-20 14:54:42 +02002311 out:
2312 return ret;
2313}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002314
Christopher Faulet61cc8522020-04-20 14:54:42 +02002315/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2316 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2317 * waits.
2318 */
2319static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2320{
2321 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2322 struct act_rule *act_rule;
2323 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002324
Christopher Faulet61cc8522020-04-20 14:54:42 +02002325 act_rule =rule->action_kw.rule;
2326 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2327 if (act_ret != ACT_RET_CONT) {
2328 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2329 tcpcheck_get_step_id(check, rule));
2330 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2331 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002332 }
2333
Christopher Faulet61cc8522020-04-20 14:54:42 +02002334 return ret;
2335}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002336
Christopher Faulet61cc8522020-04-20 14:54:42 +02002337/* Executes a tcp-check ruleset. Note that this is called both from the
2338 * connection's wake() callback and from the check scheduling task. It returns
2339 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2340 * presenting the risk of an fd replacement.
2341 *
2342 * Please do NOT place any return statement in this function and only leave
2343 * via the out_end_tcpcheck label after setting retcode.
2344 */
2345static int tcpcheck_main(struct check *check)
2346{
2347 struct tcpcheck_rule *rule;
2348 struct conn_stream *cs = check->cs;
2349 struct connection *conn = cs_conn(cs);
2350 int must_read = 1, last_read = 0;
2351 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002352
Christopher Faulet61cc8522020-04-20 14:54:42 +02002353 /* here, we know that the check is complete or that it failed */
2354 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002355 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002356
Christopher Faulet61cc8522020-04-20 14:54:42 +02002357 /* 1- check for connection error, if any */
2358 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2359 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002360
Christopher Faulet61cc8522020-04-20 14:54:42 +02002361 /* 2- check if we are waiting for the connection establishment. It only
2362 * happens during TCPCHK_ACT_CONNECT. */
2363 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2364 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2365 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2366 if (rule->action == TCPCHK_ACT_SEND)
2367 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2368 else if (rule->action == TCPCHK_ACT_EXPECT)
2369 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2370 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002371 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002372 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002373
2374 /* 3- check for pending outgoing data. It only happens during
2375 * TCPCHK_ACT_SEND. */
2376 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2377 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002378 ret = conn->mux->snd_buf(cs, &check->bo,
2379 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002380 if (ret <= 0) {
2381 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2382 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002383 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002384 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002385 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2386 goto out;
2387 }
2388 }
2389 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002390 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002391
Christopher Faulet61cc8522020-04-20 14:54:42 +02002392 /* 4- check if a rule must be resume. It happens if check->current_step
2393 * is defined. */
2394 else if (check->current_step)
2395 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002396
Christopher Faulet61cc8522020-04-20 14:54:42 +02002397 /* 5- It is the first evaluation. We must create a session and preset
2398 * tcp-check variables */
2399 else {
2400 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002401
Christopher Faulet61cc8522020-04-20 14:54:42 +02002402 /* First evaluation, create a session */
2403 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2404 if (!check->sess) {
2405 chunk_printf(&trash, "TCPCHK error allocating check session");
2406 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2407 goto out_end_tcpcheck;
2408 }
2409 vars_init(&check->vars, SCOPE_CHECK);
2410 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002411
Christopher Faulet61cc8522020-04-20 14:54:42 +02002412 /* Preset tcp-check variables */
2413 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2414 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002415
Christopher Faulet61cc8522020-04-20 14:54:42 +02002416 memset(&smp, 0, sizeof(smp));
2417 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2418 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002419 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002420 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002421 }
2422
Christopher Faulet61cc8522020-04-20 14:54:42 +02002423 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002424
Christopher Faulet61cc8522020-04-20 14:54:42 +02002425 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2426 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002427
Christopher Faulet61cc8522020-04-20 14:54:42 +02002428 check->code = 0;
2429 switch (rule->action) {
2430 case TCPCHK_ACT_CONNECT:
2431 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002432
Christopher Faulet61cc8522020-04-20 14:54:42 +02002433 /* close but not release yet previous connection */
2434 if (check->cs) {
2435 cs_close(check->cs);
2436 retcode = -1; /* do not reuse the fd in the caller! */
2437 }
2438 eval_ret = tcpcheck_eval_connect(check, rule);
2439 must_read = 1; last_read = 0;
2440 break;
2441 case TCPCHK_ACT_SEND:
2442 check->current_step = rule;
2443 eval_ret = tcpcheck_eval_send(check, rule);
2444 must_read = 1;
2445 break;
2446 case TCPCHK_ACT_EXPECT:
2447 check->current_step = rule;
2448 if (must_read) {
2449 if (check->proxy->timeout.check)
2450 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002451
Christopher Faulet61cc8522020-04-20 14:54:42 +02002452 eval_ret = tcpcheck_eval_recv(check, rule);
2453 if (eval_ret == TCPCHK_EVAL_STOP)
2454 goto out_end_tcpcheck;
2455 else if (eval_ret == TCPCHK_EVAL_WAIT)
2456 goto out;
2457 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2458 must_read = 0;
2459 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002460
Christopher Faulet61cc8522020-04-20 14:54:42 +02002461 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2462 ? tcpcheck_eval_expect_http(check, rule, last_read)
2463 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002464
Christopher Faulet61cc8522020-04-20 14:54:42 +02002465 if (eval_ret == TCPCHK_EVAL_WAIT) {
2466 check->current_step = rule->expect.head;
2467 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2468 }
2469 break;
2470 case TCPCHK_ACT_ACTION_KW:
2471 /* Don't update the current step */
2472 eval_ret = tcpcheck_eval_action_kw(check, rule);
2473 break;
2474 default:
2475 /* Otherwise, just go to the next one and don't update
2476 * the current step
2477 */
2478 eval_ret = TCPCHK_EVAL_CONTINUE;
2479 break;
2480 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002481
Christopher Faulet61cc8522020-04-20 14:54:42 +02002482 switch (eval_ret) {
2483 case TCPCHK_EVAL_CONTINUE:
2484 break;
2485 case TCPCHK_EVAL_WAIT:
2486 goto out;
2487 case TCPCHK_EVAL_STOP:
2488 goto out_end_tcpcheck;
2489 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002490 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002491
Christopher Faulet61cc8522020-04-20 14:54:42 +02002492 /* All rules was evaluated */
2493 if (check->current_step) {
2494 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002495
Christopher Faulet61cc8522020-04-20 14:54:42 +02002496 if (rule->action == TCPCHK_ACT_EXPECT) {
2497 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002498
Christopher Faulet61cc8522020-04-20 14:54:42 +02002499 if (check->server &&
2500 (check->server->proxy->options & PR_O_DISABLE404) &&
2501 (check->server->next_state != SRV_ST_STOPPED) &&
2502 (check->code == 404)) {
2503 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2504 goto out_end_tcpcheck;
2505 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002506
Christopher Faulet61cc8522020-04-20 14:54:42 +02002507 msg = alloc_trash_chunk();
2508 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002509 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002510 set_server_check_status(check, rule->expect.ok_status,
2511 (msg ? b_head(msg) : "(tcp-check)"));
2512 free_trash_chunk(msg);
2513 }
2514 else if (rule->action == TCPCHK_ACT_CONNECT) {
2515 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002516 enum healthcheck_status status = HCHK_STATUS_L4OK;
2517#ifdef USE_OPENSSL
2518 if (conn && ssl_sock_is_ssl(conn))
2519 status = HCHK_STATUS_L6OK;
2520#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002521 set_server_check_status(check, status, msg);
2522 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002523 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002524 else
2525 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002526
Christopher Faulet61cc8522020-04-20 14:54:42 +02002527 out_end_tcpcheck:
2528 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2529 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002530
Christopher Faulet61cc8522020-04-20 14:54:42 +02002531 out:
2532 return retcode;
2533}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002534
Christopher Faulet14cd3162020-04-16 14:50:06 +02002535
Christopher Faulet61cc8522020-04-20 14:54:42 +02002536/**************************************************************************/
2537/************** Health-checks based on an external process ****************/
2538/**************************************************************************/
2539static struct list pid_list = LIST_HEAD_INIT(pid_list);
2540static struct pool_head *pool_head_pid_list;
2541__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002542
Christopher Faulet61cc8522020-04-20 14:54:42 +02002543struct extcheck_env {
2544 char *name; /* environment variable name */
2545 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2546};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002547
Christopher Faulet61cc8522020-04-20 14:54:42 +02002548/* environment variables memory requirement for different types of data */
2549#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2550 * such environment variables are not updatable. */
2551#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2552#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2553#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002554
Christopher Faulet61cc8522020-04-20 14:54:42 +02002555/* external checks environment variables */
2556enum {
2557 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002558
Christopher Faulet61cc8522020-04-20 14:54:42 +02002559 /* Proxy specific environment variables */
2560 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2561 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2562 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2563 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002564
Christopher Faulet61cc8522020-04-20 14:54:42 +02002565 /* Server specific environment variables */
2566 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2567 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2568 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2569 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2570 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2571 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002572
Christopher Faulet61cc8522020-04-20 14:54:42 +02002573 EXTCHK_SIZE
2574};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002575
Christopher Faulet61cc8522020-04-20 14:54:42 +02002576const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2577 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2578 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2579 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2580 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2581 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2582 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2583 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2584 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2585 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2586 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2587 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2588};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002589
Christopher Faulet61cc8522020-04-20 14:54:42 +02002590void block_sigchld(void)
2591{
2592 sigset_t set;
2593 sigemptyset(&set);
2594 sigaddset(&set, SIGCHLD);
2595 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2596}
Willy Tarreaube373152018-09-06 11:45:30 +02002597
Christopher Faulet61cc8522020-04-20 14:54:42 +02002598void unblock_sigchld(void)
2599{
2600 sigset_t set;
2601 sigemptyset(&set);
2602 sigaddset(&set, SIGCHLD);
2603 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002604}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002605
Christopher Faulet61cc8522020-04-20 14:54:42 +02002606static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002607{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002608 struct pid_list *elem;
2609 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002610
Christopher Faulet61cc8522020-04-20 14:54:42 +02002611 elem = pool_alloc(pool_head_pid_list);
2612 if (!elem)
2613 return NULL;
2614 elem->pid = pid;
2615 elem->t = t;
2616 elem->exited = 0;
2617 check->curpid = elem;
2618 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002619
Christopher Faulet61cc8522020-04-20 14:54:42 +02002620 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2621 LIST_ADD(&pid_list, &elem->list);
2622 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002623
Christopher Faulet61cc8522020-04-20 14:54:42 +02002624 return elem;
2625}
Christopher Faulete5870d82020-04-15 11:32:03 +02002626
Christopher Faulet61cc8522020-04-20 14:54:42 +02002627static void pid_list_del(struct pid_list *elem)
2628{
2629 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002630
Christopher Faulet61cc8522020-04-20 14:54:42 +02002631 if (!elem)
2632 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002633
Christopher Faulet61cc8522020-04-20 14:54:42 +02002634 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2635 LIST_DEL(&elem->list);
2636 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002637
Christopher Faulet61cc8522020-04-20 14:54:42 +02002638 if (!elem->exited)
2639 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002640
Christopher Faulet61cc8522020-04-20 14:54:42 +02002641 check = elem->t->context;
2642 check->curpid = NULL;
2643 pool_free(pool_head_pid_list, elem);
2644}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002645
Christopher Faulet61cc8522020-04-20 14:54:42 +02002646/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2647static void pid_list_expire(pid_t pid, int status)
2648{
2649 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002650
Christopher Faulet61cc8522020-04-20 14:54:42 +02002651 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2652 list_for_each_entry(elem, &pid_list, list) {
2653 if (elem->pid == pid) {
2654 elem->t->expire = now_ms;
2655 elem->status = status;
2656 elem->exited = 1;
2657 task_wakeup(elem->t, TASK_WOKEN_IO);
2658 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002659 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002660 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002661 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2662}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002663
Christopher Faulet61cc8522020-04-20 14:54:42 +02002664static void sigchld_handler(struct sig_handler *sh)
2665{
2666 pid_t pid;
2667 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002668
Christopher Faulet61cc8522020-04-20 14:54:42 +02002669 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2670 pid_list_expire(pid, status);
2671}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002672
Christopher Faulet61cc8522020-04-20 14:54:42 +02002673static int init_pid_list(void)
2674{
2675 if (pool_head_pid_list != NULL)
2676 /* Nothing to do */
2677 return 0;
2678
2679 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2680 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2681 strerror(errno));
2682 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002683 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002684
Christopher Faulet61cc8522020-04-20 14:54:42 +02002685 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2686 if (pool_head_pid_list == NULL) {
2687 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2688 strerror(errno));
2689 return 1;
2690 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002691
Christopher Faulet61cc8522020-04-20 14:54:42 +02002692 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002693}
2694
Christopher Faulet61cc8522020-04-20 14:54:42 +02002695/* helper macro to set an environment variable and jump to a specific label on failure. */
2696#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002697
Christopher Faulet61cc8522020-04-20 14:54:42 +02002698/*
2699 * helper function to allocate enough memory to store an environment variable.
2700 * It will also check that the environment variable is updatable, and silently
2701 * fail if not.
2702 */
2703static int extchk_setenv(struct check *check, int idx, const char *value)
2704{
2705 int len, ret;
2706 char *envname;
2707 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002708
Christopher Faulet61cc8522020-04-20 14:54:42 +02002709 if (idx < 0 || idx >= EXTCHK_SIZE) {
2710 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2711 return 1;
2712 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002713
Christopher Faulet61cc8522020-04-20 14:54:42 +02002714 envname = extcheck_envs[idx].name;
2715 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002716
Christopher Faulet61cc8522020-04-20 14:54:42 +02002717 /* Check if the environment variable is already set, and silently reject
2718 * the update if this one is not updatable. */
2719 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2720 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002721
Christopher Faulet61cc8522020-04-20 14:54:42 +02002722 /* Instead of sending NOT_USED, sending an empty value is preferable */
2723 if (strcmp(value, "NOT_USED") == 0) {
2724 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002725 }
2726
Christopher Faulet61cc8522020-04-20 14:54:42 +02002727 len = strlen(envname) + 1;
2728 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2729 len += strlen(value);
2730 else
2731 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002732
Christopher Faulet61cc8522020-04-20 14:54:42 +02002733 if (!check->envp[idx])
2734 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002735
Christopher Faulet61cc8522020-04-20 14:54:42 +02002736 if (!check->envp[idx]) {
2737 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2738 return 1;
2739 }
2740 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2741 if (ret < 0) {
2742 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2743 return 1;
2744 }
2745 else if (ret > len) {
2746 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2747 return 1;
2748 }
2749 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002750}
2751
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002753{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002754 struct server *s = check->server;
2755 struct proxy *px = s->proxy;
2756 struct listener *listener = NULL, *l;
2757 int i;
2758 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2759 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002760
Christopher Faulet61cc8522020-04-20 14:54:42 +02002761 list_for_each_entry(l, &px->conf.listeners, by_fe)
2762 /* Use the first INET, INET6 or UNIX listener */
2763 if (l->addr.ss_family == AF_INET ||
2764 l->addr.ss_family == AF_INET6 ||
2765 l->addr.ss_family == AF_UNIX) {
2766 listener = l;
2767 break;
2768 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002769
Christopher Faulet61cc8522020-04-20 14:54:42 +02002770 check->curpid = NULL;
2771 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2772 if (!check->envp) {
2773 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2774 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002775 }
2776
Christopher Faulet61cc8522020-04-20 14:54:42 +02002777 check->argv = calloc(6, sizeof(char *));
2778 if (!check->argv) {
2779 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2780 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002781 }
2782
Christopher Faulet61cc8522020-04-20 14:54:42 +02002783 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002784
Christopher Faulet61cc8522020-04-20 14:54:42 +02002785 if (!listener) {
2786 check->argv[1] = strdup("NOT_USED");
2787 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002788 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002789 else if (listener->addr.ss_family == AF_INET ||
2790 listener->addr.ss_family == AF_INET6) {
2791 addr_to_str(&listener->addr, buf, sizeof(buf));
2792 check->argv[1] = strdup(buf);
2793 port_to_str(&listener->addr, buf, sizeof(buf));
2794 check->argv[2] = strdup(buf);
2795 }
2796 else if (listener->addr.ss_family == AF_UNIX) {
2797 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002798
Christopher Faulet61cc8522020-04-20 14:54:42 +02002799 un = (struct sockaddr_un *)&listener->addr;
2800 check->argv[1] = strdup(un->sun_path);
2801 check->argv[2] = strdup("NOT_USED");
2802 }
2803 else {
2804 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2805 goto err;
2806 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002807
Christopher Faulet61cc8522020-04-20 14:54:42 +02002808 if (!check->argv[1] || !check->argv[2]) {
2809 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2810 goto err;
2811 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002812
Christopher Faulet61cc8522020-04-20 14:54:42 +02002813 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2814 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2815 if (!check->argv[3] || !check->argv[4]) {
2816 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2817 goto err;
2818 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002819
Christopher Faulet61cc8522020-04-20 14:54:42 +02002820 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2821 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2822 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002823
Christopher Faulet61cc8522020-04-20 14:54:42 +02002824 for (i = 0; i < 5; i++) {
2825 if (!check->argv[i]) {
2826 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2827 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002828 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002829 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002830
Christopher Faulet61cc8522020-04-20 14:54:42 +02002831 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2832 /* Add proxy environment variables */
2833 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2834 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2835 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2836 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2837 /* Add server environment variables */
2838 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2839 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2840 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2841 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2842 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2843 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002844
Christopher Faulet61cc8522020-04-20 14:54:42 +02002845 /* Ensure that we don't leave any hole in check->envp */
2846 for (i = 0; i < EXTCHK_SIZE; i++)
2847 if (!check->envp[i])
2848 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002849
Christopher Faulet61cc8522020-04-20 14:54:42 +02002850 return 1;
2851err:
2852 if (check->envp) {
2853 for (i = 0; i < EXTCHK_SIZE; i++)
2854 free(check->envp[i]);
2855 free(check->envp);
2856 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002857 }
2858
Christopher Faulet61cc8522020-04-20 14:54:42 +02002859 if (check->argv) {
2860 for (i = 1; i < 5; i++)
2861 free(check->argv[i]);
2862 free(check->argv);
2863 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002864 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002865 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002866}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002867
Christopher Faulet61cc8522020-04-20 14:54:42 +02002868/*
2869 * establish a server health-check that makes use of a process.
2870 *
2871 * It can return one of :
2872 * - SF_ERR_NONE if everything's OK
2873 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2874 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2875 *
2876 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002877 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002878static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002879{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002880 char buf[256];
2881 struct check *check = t->context;
2882 struct server *s = check->server;
2883 struct proxy *px = s->proxy;
2884 int status;
2885 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002886
Christopher Faulet61cc8522020-04-20 14:54:42 +02002887 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002888
Christopher Faulet61cc8522020-04-20 14:54:42 +02002889 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002890
Christopher Faulet61cc8522020-04-20 14:54:42 +02002891 pid = fork();
2892 if (pid < 0) {
2893 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2894 (global.tune.options & GTUNE_INSECURE_FORK) ?
2895 "" : " (likely caused by missing 'insecure-fork-wanted')",
2896 strerror(errno));
2897 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002898 goto out;
2899 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002900 if (pid == 0) {
2901 /* Child */
2902 extern char **environ;
2903 struct rlimit limit;
2904 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002905
Christopher Faulet61cc8522020-04-20 14:54:42 +02002906 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2907 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002908
Christopher Faulet61cc8522020-04-20 14:54:42 +02002909 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002910
Christopher Faulet61cc8522020-04-20 14:54:42 +02002911 /* restore the initial FD limits */
2912 limit.rlim_cur = rlim_fd_cur_at_boot;
2913 limit.rlim_max = rlim_fd_max_at_boot;
2914 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2915 getrlimit(RLIMIT_NOFILE, &limit);
2916 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2917 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2918 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2919 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002920
Christopher Faulet61cc8522020-04-20 14:54:42 +02002921 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002922
Christopher Faulet61cc8522020-04-20 14:54:42 +02002923 /* Update some environment variables and command args: curconn, server addr and server port */
2924 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002925
Christopher Faulet61cc8522020-04-20 14:54:42 +02002926 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2927 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002928
Christopher Faulet61cc8522020-04-20 14:54:42 +02002929 *check->argv[4] = 0;
2930 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2931 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2932 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002933
Christopher Faulet61cc8522020-04-20 14:54:42 +02002934 haproxy_unblock_signals();
2935 execvp(px->check_command, check->argv);
2936 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2937 strerror(errno));
2938 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002939 }
2940
Christopher Faulet61cc8522020-04-20 14:54:42 +02002941 /* Parent */
2942 if (check->result == CHK_RES_UNKNOWN) {
2943 if (pid_list_add(pid, t) != NULL) {
2944 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2945
2946 if (px->timeout.check && px->timeout.connect) {
2947 int t_con = tick_add(now_ms, px->timeout.connect);
2948 t->expire = tick_first(t->expire, t_con);
2949 }
2950 status = SF_ERR_NONE;
2951 goto out;
2952 }
2953 else {
2954 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2955 }
2956 kill(pid, SIGTERM); /* process creation error */
2957 }
2958 else
2959 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2960
2961out:
2962 unblock_sigchld();
2963 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002964}
2965
Christopher Faulet61cc8522020-04-20 14:54:42 +02002966/*
2967 * manages a server health-check that uses an external process. Returns
2968 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002969 *
2970 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002971 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002972 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002973static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002974{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002975 struct check *check = context;
2976 struct server *s = check->server;
2977 int rv;
2978 int ret;
2979 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002980
Christopher Faulet61cc8522020-04-20 14:54:42 +02002981 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2982 if (!(check->state & CHK_ST_INPROGRESS)) {
2983 /* no check currently running */
2984 if (!expired) /* woke up too early */
2985 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002986
Christopher Faulet61cc8522020-04-20 14:54:42 +02002987 /* we don't send any health-checks when the proxy is
2988 * stopped, the server should not be checked or the check
2989 * is disabled.
2990 */
2991 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2992 s->proxy->state == PR_STSTOPPED)
2993 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002994
Christopher Faulet61cc8522020-04-20 14:54:42 +02002995 /* we'll initiate a new check */
2996 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002997
Christopher Faulet61cc8522020-04-20 14:54:42 +02002998 check->state |= CHK_ST_INPROGRESS;
2999
3000 ret = connect_proc_chk(t);
3001 if (ret == SF_ERR_NONE) {
3002 /* the process was forked, we allow up to min(inter,
3003 * timeout.connect) for it to report its status, but
3004 * only when timeout.check is set as it may be to short
3005 * for a full check otherwise.
3006 */
3007 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3008
3009 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3010 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3011 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003012 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003013 task_set_affinity(t, tid_bit);
3014 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003015 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003016
Christopher Faulet61cc8522020-04-20 14:54:42 +02003017 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003018
Christopher Faulet61cc8522020-04-20 14:54:42 +02003019 check->state &= ~CHK_ST_INPROGRESS;
3020 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003021
Christopher Faulet61cc8522020-04-20 14:54:42 +02003022 /* we allow up to min(inter, timeout.connect) for a connection
3023 * to establish but only when timeout.check is set
3024 * as it may be to short for a full check otherwise
3025 */
3026 while (tick_is_expired(t->expire, now_ms)) {
3027 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003028
Christopher Faulet61cc8522020-04-20 14:54:42 +02003029 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3030 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003031
Christopher Faulet61cc8522020-04-20 14:54:42 +02003032 if (s->proxy->timeout.check)
3033 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003034 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003035 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003036 else {
3037 /* there was a test running.
3038 * First, let's check whether there was an uncaught error,
3039 * which can happen on connect timeout or error.
3040 */
3041 if (check->result == CHK_RES_UNKNOWN) {
3042 /* good connection is enough for pure TCP check */
3043 struct pid_list *elem = check->curpid;
3044 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003045
Christopher Faulet61cc8522020-04-20 14:54:42 +02003046 if (elem->exited) {
3047 status = elem->status; /* Save in case the process exits between use below */
3048 if (!WIFEXITED(status))
3049 check->code = -1;
3050 else
3051 check->code = WEXITSTATUS(status);
3052 if (!WIFEXITED(status) || WEXITSTATUS(status))
3053 status = HCHK_STATUS_PROCERR;
3054 else
3055 status = HCHK_STATUS_PROCOK;
3056 } else if (expired) {
3057 status = HCHK_STATUS_PROCTOUT;
3058 ha_warning("kill %d\n", (int)elem->pid);
3059 kill(elem->pid, SIGTERM);
3060 }
3061 set_server_check_status(check, status, NULL);
3062 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003063
Christopher Faulet61cc8522020-04-20 14:54:42 +02003064 if (check->result == CHK_RES_FAILED) {
3065 /* a failure or timeout detected */
3066 check_notify_failure(check);
3067 }
3068 else if (check->result == CHK_RES_CONDPASS) {
3069 /* check is OK but asks for stopping mode */
3070 check_notify_stopping(check);
3071 }
3072 else if (check->result == CHK_RES_PASSED) {
3073 /* a success was detected */
3074 check_notify_success(check);
3075 }
3076 task_set_affinity(t, 1);
3077 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003078
Christopher Faulet61cc8522020-04-20 14:54:42 +02003079 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003080
Christopher Faulet61cc8522020-04-20 14:54:42 +02003081 rv = 0;
3082 if (global.spread_checks > 0) {
3083 rv = srv_getinter(check) * global.spread_checks / 100;
3084 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3085 }
3086 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3087 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003088
Christopher Faulet61cc8522020-04-20 14:54:42 +02003089 reschedule:
3090 while (tick_is_expired(t->expire, now_ms))
3091 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003092
Christopher Faulet61cc8522020-04-20 14:54:42 +02003093 out_unlock:
3094 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3095 return t;
3096}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003097
Baptiste Assmann248f1172018-03-01 21:49:01 +01003098
Christopher Faulet61cc8522020-04-20 14:54:42 +02003099/**************************************************************************/
3100/***************** Health-checks based on connections *********************/
3101/**************************************************************************/
3102/* This function is used only for server health-checks. It handles connection
3103 * status updates including errors. If necessary, it wakes the check task up.
3104 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3105 * connection (eg: reconnect). It relies on tcpcheck_main().
3106 */
3107static int wake_srv_chk(struct conn_stream *cs)
3108{
3109 struct connection *conn = cs->conn;
3110 struct check *check = cs->data;
3111 struct email_alertq *q = container_of(check, typeof(*q), check);
3112 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003113
Christopher Faulet61cc8522020-04-20 14:54:42 +02003114 if (check->server)
3115 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3116 else
3117 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003118
Christopher Faulet61cc8522020-04-20 14:54:42 +02003119 /* we may have to make progress on the TCP checks */
3120 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003121
Christopher Faulet61cc8522020-04-20 14:54:42 +02003122 cs = check->cs;
3123 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003124
Christopher Faulet61cc8522020-04-20 14:54:42 +02003125 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3126 /* We may get error reports bypassing the I/O handlers, typically
3127 * the case when sending a pure TCP check which fails, then the I/O
3128 * handlers above are not called. This is completely handled by the
3129 * main processing task so let's simply wake it up. If we get here,
3130 * we expect errno to still be valid.
3131 */
3132 chk_report_conn_err(check, errno, 0);
3133 task_wakeup(check->task, TASK_WOKEN_IO);
3134 }
3135
3136 if (check->result != CHK_RES_UNKNOWN) {
3137 /* Check complete or aborted. If connection not yet closed do it
3138 * now and wake the check task up to be sure the result is
3139 * handled ASAP. */
3140 conn_sock_drain(conn);
3141 cs_close(cs);
3142 ret = -1;
3143 /* We may have been scheduled to run, and the
3144 * I/O handler expects to have a cs, so remove
3145 * the tasklet
3146 */
3147 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3148 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003149 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003150
3151 if (check->server)
3152 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003153 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003154 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003155
Christopher Faulet61cc8522020-04-20 14:54:42 +02003156 /* if a connection got replaced, we must absolutely prevent the connection
3157 * handler from touching its fd, and perform the FD polling updates ourselves
3158 */
3159 if (ret < 0)
3160 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003161
Christopher Faulet61cc8522020-04-20 14:54:42 +02003162 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003163}
3164
Christopher Faulet61cc8522020-04-20 14:54:42 +02003165/* This function checks if any I/O is wanted, and if so, attempts to do so */
3166static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003167{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003168 struct check *check = ctx;
3169 struct conn_stream *cs = check->cs;
3170 struct email_alertq *q = container_of(check, typeof(*q), check);
3171 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003172
Christopher Faulet61cc8522020-04-20 14:54:42 +02003173 if (!(check->wait_list.events & SUB_RETRY_SEND))
3174 ret = wake_srv_chk(cs);
3175 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3176 if (check->server)
3177 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3178 else
3179 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003180
Christopher Faulet61cc8522020-04-20 14:54:42 +02003181 if (unlikely(check->result == CHK_RES_FAILED)) {
3182 /* collect possible new errors */
3183 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3184 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003185
Christopher Faulet61cc8522020-04-20 14:54:42 +02003186 /* Reset the check buffer... */
3187 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003188
Christopher Faulet61cc8522020-04-20 14:54:42 +02003189 /* Close the connection... We still attempt to nicely close if,
3190 * for instance, SSL needs to send a "close notify." Later, we perform
3191 * a hard close and reset the connection if some data are pending,
3192 * otherwise we end up with many TIME_WAITs and eat all the source port
3193 * range quickly. To avoid sending RSTs all the time, we first try to
3194 * drain pending data.
3195 */
3196 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3197 * connection, to make sure cs_shutw() will not lead to a shutdown()
3198 * that would provoke TIME_WAITs.
3199 */
3200 cs_shutr(cs, CS_SHR_DRAIN);
3201 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003202
Christopher Faulet61cc8522020-04-20 14:54:42 +02003203 /* OK, let's not stay here forever */
3204 if (check->result == CHK_RES_FAILED)
3205 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003206
Christopher Faulet61cc8522020-04-20 14:54:42 +02003207 task_wakeup(t, TASK_WOKEN_IO);
3208 }
3209
3210 if (check->server)
3211 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3212 else
3213 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003214 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003215 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003216}
3217
Christopher Faulet61cc8522020-04-20 14:54:42 +02003218/* manages a server health-check that uses a connection. Returns
3219 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3220 *
3221 * Please do NOT place any return statement in this function and only leave
3222 * via the out_unlock label.
3223 */
3224static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003225{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003226 struct check *check = context;
3227 struct proxy *proxy = check->proxy;
3228 struct conn_stream *cs = check->cs;
3229 struct connection *conn = cs_conn(cs);
3230 int rv;
3231 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003232
Christopher Faulet61cc8522020-04-20 14:54:42 +02003233 if (check->server)
3234 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3235 if (!(check->state & CHK_ST_INPROGRESS)) {
3236 /* no check currently running */
3237 if (!expired) /* woke up too early */
3238 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003239
Christopher Faulet61cc8522020-04-20 14:54:42 +02003240 /* we don't send any health-checks when the proxy is
3241 * stopped, the server should not be checked or the check
3242 * is disabled.
3243 */
3244 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3245 proxy->state == PR_STSTOPPED)
3246 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003247
Christopher Faulet61cc8522020-04-20 14:54:42 +02003248 /* we'll initiate a new check */
3249 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003250
Christopher Faulet61cc8522020-04-20 14:54:42 +02003251 check->state |= CHK_ST_INPROGRESS;
3252 b_reset(&check->bi);
3253 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003254
Christopher Faulet61cc8522020-04-20 14:54:42 +02003255 task_set_affinity(t, tid_bit);
3256 cs = check->cs;
3257 conn = cs_conn(cs);
3258 if (!conn) {
3259 check->current_step = NULL;
3260 tcpcheck_main(check);
3261 goto out_unlock;
3262 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003263
Christopher Faulet61cc8522020-04-20 14:54:42 +02003264 conn->flags |= CO_FL_ERROR;
3265 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003266
Christopher Faulet61cc8522020-04-20 14:54:42 +02003267 /* here, we have seen a synchronous error, no fd was allocated */
3268 task_set_affinity(t, MAX_THREADS_MASK);
3269 if (cs) {
3270 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003271 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003272 /* We may have been scheduled to run, and the
3273 * I/O handler expects to have a cs, so remove
3274 * the tasklet
3275 */
3276 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3277 cs_destroy(cs);
3278 cs = check->cs = NULL;
3279 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003280 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003281
Christopher Faulet61cc8522020-04-20 14:54:42 +02003282 check->state &= ~CHK_ST_INPROGRESS;
3283 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003284
Christopher Faulet61cc8522020-04-20 14:54:42 +02003285 /* we allow up to min(inter, timeout.connect) for a connection
3286 * to establish but only when timeout.check is set
3287 * as it may be to short for a full check otherwise
3288 */
3289 while (tick_is_expired(t->expire, now_ms)) {
3290 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003291
Christopher Faulet61cc8522020-04-20 14:54:42 +02003292 t_con = tick_add(t->expire, proxy->timeout.connect);
3293 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3294 if (proxy->timeout.check)
3295 t->expire = tick_first(t->expire, t_con);
3296 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003297 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003298 else {
3299 /* there was a test running.
3300 * First, let's check whether there was an uncaught error,
3301 * which can happen on connect timeout or error.
3302 */
3303 if (check->result == CHK_RES_UNKNOWN) {
3304 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3305 chk_report_conn_err(check, 0, expired);
3306 }
3307 else
3308 goto out_unlock; /* timeout not reached, wait again */
3309 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003310
Christopher Faulet61cc8522020-04-20 14:54:42 +02003311 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003312
Christopher Faulet61cc8522020-04-20 14:54:42 +02003313 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003314
Christopher Faulet61cc8522020-04-20 14:54:42 +02003315 if (conn && conn->xprt) {
3316 /* The check was aborted and the connection was not yet closed.
3317 * This can happen upon timeout, or when an external event such
3318 * as a failed response coupled with "observe layer7" caused the
3319 * server state to be suddenly changed.
3320 */
3321 conn_sock_drain(conn);
3322 cs_close(cs);
3323 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003324
Christopher Faulet61cc8522020-04-20 14:54:42 +02003325 if (cs) {
3326 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003327 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003328 /* We may have been scheduled to run, and the
3329 * I/O handler expects to have a cs, so remove
3330 * the tasklet
3331 */
3332 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3333 cs_destroy(cs);
3334 cs = check->cs = NULL;
3335 conn = NULL;
3336 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003337
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003338 if (check->sess != NULL) {
3339 vars_prune(&check->vars, check->sess, NULL);
3340 session_free(check->sess);
3341 check->sess = NULL;
3342 }
3343
Christopher Faulet61cc8522020-04-20 14:54:42 +02003344 if (check->server) {
3345 if (check->result == CHK_RES_FAILED) {
3346 /* a failure or timeout detected */
3347 check_notify_failure(check);
3348 }
3349 else if (check->result == CHK_RES_CONDPASS) {
3350 /* check is OK but asks for stopping mode */
3351 check_notify_stopping(check);
3352 }
3353 else if (check->result == CHK_RES_PASSED) {
3354 /* a success was detected */
3355 check_notify_success(check);
3356 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003357 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003358 task_set_affinity(t, MAX_THREADS_MASK);
3359 check->state &= ~CHK_ST_INPROGRESS;
3360
3361 if (check->server) {
3362 rv = 0;
3363 if (global.spread_checks > 0) {
3364 rv = srv_getinter(check) * global.spread_checks / 100;
3365 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3366 }
3367 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003368 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003369 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003370
Christopher Faulet61cc8522020-04-20 14:54:42 +02003371 reschedule:
3372 while (tick_is_expired(t->expire, now_ms))
3373 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3374 out_unlock:
3375 if (check->server)
3376 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3377 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003378}
3379
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003380
Christopher Faulet61cc8522020-04-20 14:54:42 +02003381/**************************************************************************/
3382/******************* Internals to parse tcp-check rules *******************/
3383/**************************************************************************/
3384struct action_kw_list tcp_check_keywords = {
3385 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3386};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003387
Christopher Faulet61cc8522020-04-20 14:54:42 +02003388/* Return the struct action_kw associated to a keyword */
3389static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003390{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003391 return action_lookup(&tcp_check_keywords.list, kw);
3392}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003393
Christopher Faulet61cc8522020-04-20 14:54:42 +02003394static void action_kw_tcp_check_build_list(struct buffer *chk)
3395{
3396 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003397}
3398
Christopher Faulet61cc8522020-04-20 14:54:42 +02003399/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3400 * returned on error.
3401 */
3402static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3403 struct list *rules, struct action_kw *kw,
3404 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003405{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003406 struct tcpcheck_rule *chk = NULL;
3407 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003408
Christopher Faulet61cc8522020-04-20 14:54:42 +02003409 actrule = calloc(1, sizeof(*actrule));
3410 if (!actrule) {
3411 memprintf(errmsg, "out of memory");
3412 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003413 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003414 actrule->kw = kw;
3415 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003416
Christopher Faulet61cc8522020-04-20 14:54:42 +02003417 cur_arg++;
3418 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3419 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3420 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003421 }
3422
Christopher Faulet61cc8522020-04-20 14:54:42 +02003423 chk = calloc(1, sizeof(*chk));
3424 if (!chk) {
3425 memprintf(errmsg, "out of memory");
3426 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003427 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003428 chk->action = TCPCHK_ACT_ACTION_KW;
3429 chk->action_kw.rule = actrule;
3430 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003431
3432 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003433 free(actrule);
3434 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003435}
3436
Christopher Faulet61cc8522020-04-20 14:54:42 +02003437/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3438 * returned on error.
3439 */
3440static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3441 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003442{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003443 struct tcpcheck_rule *chk = NULL;
3444 struct sockaddr_storage *sk = NULL;
3445 char *comment = NULL, *sni = NULL, *alpn = NULL;
3446 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003447 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003448 unsigned short conn_opts = 0;
3449 long port = 0;
3450 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003451
Christopher Faulet61cc8522020-04-20 14:54:42 +02003452 list_for_each_entry(chk, rules, list) {
3453 if (chk->action == TCPCHK_ACT_CONNECT)
3454 break;
3455 if (chk->action == TCPCHK_ACT_COMMENT ||
3456 chk->action == TCPCHK_ACT_ACTION_KW ||
3457 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3458 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003459
Christopher Faulet61cc8522020-04-20 14:54:42 +02003460 memprintf(errmsg, "first step MUST also be a 'connect', "
3461 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3462 "when there is a 'connect' step in the tcp-check ruleset");
3463 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003464 }
3465
Christopher Faulet61cc8522020-04-20 14:54:42 +02003466 cur_arg++;
3467 while (*(args[cur_arg])) {
3468 if (strcmp(args[cur_arg], "default") == 0)
3469 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3470 else if (strcmp(args[cur_arg], "addr") == 0) {
3471 int port1, port2;
3472 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003473
Christopher Faulet61cc8522020-04-20 14:54:42 +02003474 if (!*(args[cur_arg+1])) {
3475 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3476 goto error;
3477 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003478
Christopher Faulet61cc8522020-04-20 14:54:42 +02003479 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3480 if (!sk) {
3481 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3482 goto error;
3483 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003484
Christopher Faulet61cc8522020-04-20 14:54:42 +02003485 proto = protocol_by_family(sk->ss_family);
3486 if (!proto || !proto->connect) {
3487 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3488 args[cur_arg]);
3489 goto error;
3490 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003491
Christopher Faulet61cc8522020-04-20 14:54:42 +02003492 if (port1 != port2) {
3493 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3494 args[cur_arg], args[cur_arg+1]);
3495 goto error;
3496 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003497
Christopher Faulet61cc8522020-04-20 14:54:42 +02003498 cur_arg++;
3499 }
3500 else if (strcmp(args[cur_arg], "port") == 0) {
3501 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003502
Christopher Faulet61cc8522020-04-20 14:54:42 +02003503 if (!*(args[cur_arg+1])) {
3504 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3505 goto error;
3506 }
3507 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003508
Christopher Faulet61cc8522020-04-20 14:54:42 +02003509 port = 0;
3510 release_sample_expr(port_expr);
3511 p = args[cur_arg]; end = p + strlen(p);
3512 port = read_uint(&p, end);
3513 if (p != end) {
3514 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003515
Christopher Faulet61cc8522020-04-20 14:54:42 +02003516 px->conf.args.ctx = ARGC_SRV;
3517 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3518 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003519
Christopher Faulet61cc8522020-04-20 14:54:42 +02003520 if (!port_expr) {
3521 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3522 goto error;
3523 }
3524 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3525 memprintf(errmsg, "error detected while parsing port expression : "
3526 " fetch method '%s' extracts information from '%s', "
3527 "none of which is available here.\n",
3528 args[cur_arg], sample_src_names(port_expr->fetch->use));
3529 goto error;
3530 }
3531 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3532 }
3533 else if (port > 65535 || port < 1) {
3534 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3535 args[cur_arg]);
3536 goto error;
3537 }
3538 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003539 else if (strcmp(args[cur_arg], "proto") == 0) {
3540 if (!*(args[cur_arg+1])) {
3541 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3542 goto error;
3543 }
3544 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3545 if (!mux_proto) {
3546 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3547 goto error;
3548 }
3549 cur_arg++;
3550 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003551 else if (strcmp(args[cur_arg], "comment") == 0) {
3552 if (!*(args[cur_arg+1])) {
3553 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3554 goto error;
3555 }
3556 cur_arg++;
3557 free(comment);
3558 comment = strdup(args[cur_arg]);
3559 if (!comment) {
3560 memprintf(errmsg, "out of memory");
3561 goto error;
3562 }
3563 }
3564 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3565 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3566 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3567 conn_opts |= TCPCHK_OPT_SOCKS4;
3568 else if (strcmp(args[cur_arg], "linger") == 0)
3569 conn_opts |= TCPCHK_OPT_LINGER;
3570#ifdef USE_OPENSSL
3571 else if (strcmp(args[cur_arg], "ssl") == 0) {
3572 px->options |= PR_O_TCPCHK_SSL;
3573 conn_opts |= TCPCHK_OPT_SSL;
3574 }
3575 else if (strcmp(args[cur_arg], "sni") == 0) {
3576 if (!*(args[cur_arg+1])) {
3577 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3578 goto error;
3579 }
3580 cur_arg++;
3581 free(sni);
3582 sni = strdup(args[cur_arg]);
3583 if (!sni) {
3584 memprintf(errmsg, "out of memory");
3585 goto error;
3586 }
3587 }
3588 else if (strcmp(args[cur_arg], "alpn") == 0) {
3589#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3590 free(alpn);
3591 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3592 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3593 goto error;
3594 }
3595 cur_arg++;
3596#else
3597 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003598 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003599#endif
3600 }
3601#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003602
Christopher Faulet61cc8522020-04-20 14:54:42 +02003603 else {
3604 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3605#ifdef USE_OPENSSL
3606 ", 'ssl', 'sni', 'alpn'"
3607#endif /* USE_OPENSSL */
3608 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3609 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003610 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003611 }
3612 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003613 }
3614
Christopher Faulet61cc8522020-04-20 14:54:42 +02003615 chk = calloc(1, sizeof(*chk));
3616 if (!chk) {
3617 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003618 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003619 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003620 chk->action = TCPCHK_ACT_CONNECT;
3621 chk->comment = comment;
3622 chk->connect.port = port;
3623 chk->connect.options = conn_opts;
3624 chk->connect.sni = sni;
3625 chk->connect.alpn = alpn;
3626 chk->connect.alpn_len= alpn_len;
3627 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003628 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003629 if (sk)
3630 chk->connect.addr = *sk;
3631 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003632
Christopher Faulet61cc8522020-04-20 14:54:42 +02003633 error:
3634 free(alpn);
3635 free(sni);
3636 free(comment);
3637 release_sample_expr(port_expr);
3638 return NULL;
3639}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003640
Christopher Faulet61cc8522020-04-20 14:54:42 +02003641/* Parses and creates a tcp-check send rule. NULL is returned on error */
3642static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3643 const char *file, int line, char **errmsg)
3644{
3645 struct tcpcheck_rule *chk = NULL;
3646 char *comment = NULL, *data = NULL;
3647 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003648
Christopher Faulet61cc8522020-04-20 14:54:42 +02003649 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3650 if (!*(args[cur_arg+1])) {
3651 memprintf(errmsg, "'%s' expects a %s as argument",
3652 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003653 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003654 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003655
Christopher Faulet61cc8522020-04-20 14:54:42 +02003656 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003657
Christopher Faulet61cc8522020-04-20 14:54:42 +02003658 cur_arg += 2;
3659 while (*(args[cur_arg])) {
3660 if (strcmp(args[cur_arg], "comment") == 0) {
3661 if (!*(args[cur_arg+1])) {
3662 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3663 goto error;
3664 }
3665 cur_arg++;
3666 free(comment);
3667 comment = strdup(args[cur_arg]);
3668 if (!comment) {
3669 memprintf(errmsg, "out of memory");
3670 goto error;
3671 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003672 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003673 else if (strcmp(args[cur_arg], "log-format") == 0) {
3674 if (type == TCPCHK_SEND_BINARY)
3675 type = TCPCHK_SEND_BINARY_LF;
3676 else if (type == TCPCHK_SEND_STRING)
3677 type = TCPCHK_SEND_STRING_LF;
3678 }
3679 else {
3680 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3681 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003682 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003683 }
3684 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003685 }
3686
Christopher Faulet61cc8522020-04-20 14:54:42 +02003687 chk = calloc(1, sizeof(*chk));
3688 if (!chk) {
3689 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003690 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003691 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003692 chk->action = TCPCHK_ACT_SEND;
3693 chk->comment = comment;
3694 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003695
Christopher Faulet61cc8522020-04-20 14:54:42 +02003696 switch (chk->send.type) {
3697 case TCPCHK_SEND_STRING:
3698 chk->send.data = ist2(strdup(data), strlen(data));
3699 if (!isttest(chk->send.data)) {
3700 memprintf(errmsg, "out of memory");
3701 goto error;
3702 }
3703 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003704 case TCPCHK_SEND_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02003705 int len = chk->send.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003706 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003707 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3708 goto error;
3709 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003710 chk->send.data.len = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003711 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003712 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003713 case TCPCHK_SEND_STRING_LF:
3714 case TCPCHK_SEND_BINARY_LF:
3715 LIST_INIT(&chk->send.fmt);
3716 px->conf.args.ctx = ARGC_SRV;
3717 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3718 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3719 goto error;
3720 }
3721 break;
3722 case TCPCHK_SEND_HTTP:
3723 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003724 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003725 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003726
Christopher Faulet61cc8522020-04-20 14:54:42 +02003727 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003728
Christopher Faulet61cc8522020-04-20 14:54:42 +02003729 error:
3730 free(chk);
3731 free(comment);
3732 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003733}
3734
Christopher Faulet61cc8522020-04-20 14:54:42 +02003735/* Parses and creates a http-check send rule. NULL is returned on error */
3736static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3737 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003738{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003739 struct tcpcheck_rule *chk = NULL;
3740 struct tcpcheck_http_hdr *hdr = NULL;
3741 struct http_hdr hdrs[global.tune.max_http_hdr];
3742 char *meth = NULL, *uri = NULL, *vsn = NULL;
3743 char *body = NULL, *comment = NULL;
3744 unsigned int flags = 0;
3745 int i = 0;
3746
3747 cur_arg++;
3748 while (*(args[cur_arg])) {
3749 if (strcmp(args[cur_arg], "meth") == 0) {
3750 if (!*(args[cur_arg+1])) {
3751 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3752 goto error;
3753 }
3754 cur_arg++;
3755 meth = args[cur_arg];
3756 }
3757 else if (strcmp(args[cur_arg], "uri") == 0) {
3758 if (!*(args[cur_arg+1])) {
3759 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3760 goto error;
3761 }
3762 cur_arg++;
3763 uri = args[cur_arg];
3764 // TODO: log-format uri
3765 }
Christopher Faulet907701b2020-04-28 09:37:00 +02003766 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003767 if (!*(args[cur_arg+1])) {
3768 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3769 goto error;
3770 }
3771 cur_arg++;
3772 vsn = args[cur_arg];
3773 }
3774 else if (strcmp(args[cur_arg], "hdr") == 0) {
3775 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3776 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3777 goto error;
3778 }
3779 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3780 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3781 i++;
3782 cur_arg += 2;
3783 }
3784 else if (strcmp(args[cur_arg], "body") == 0) {
3785 if (!*(args[cur_arg+1])) {
3786 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3787 goto error;
3788 }
3789 cur_arg++;
3790 body = args[cur_arg];
3791 // TODO: log-format body
3792 }
3793 else if (strcmp(args[cur_arg], "comment") == 0) {
3794 if (!*(args[cur_arg+1])) {
3795 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3796 goto error;
3797 }
3798 cur_arg++;
3799 free(comment);
3800 comment = strdup(args[cur_arg]);
3801 if (!comment) {
3802 memprintf(errmsg, "out of memory");
3803 goto error;
3804 }
3805 }
3806 else {
Christopher Faulet907701b2020-04-28 09:37:00 +02003807 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'ver', 'hdr' and 'body' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003808 args[cur_arg]);
3809 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003811 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003812 }
3813
Christopher Faulet61cc8522020-04-20 14:54:42 +02003814 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815
Christopher Faulet61cc8522020-04-20 14:54:42 +02003816 chk = calloc(1, sizeof(*chk));
3817 if (!chk) {
3818 memprintf(errmsg, "out of memory");
3819 goto error;
3820 }
3821 chk->action = TCPCHK_ACT_SEND;
3822 chk->comment = comment; comment = NULL;
3823 chk->send.type = TCPCHK_SEND_HTTP;
3824 chk->send.http.flags = flags;
3825 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826
Christopher Faulet61cc8522020-04-20 14:54:42 +02003827 if (meth) {
3828 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3829 chk->send.http.meth.str.area = strdup(meth);
3830 chk->send.http.meth.str.data = strlen(meth);
3831 if (!chk->send.http.meth.str.area) {
3832 memprintf(errmsg, "out of memory");
3833 goto error;
3834 }
3835 }
3836 if (uri) {
3837 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3838 if (!isttest(chk->send.http.uri)) {
3839 memprintf(errmsg, "out of memory");
3840 goto error;
3841 }
3842 }
3843 if (vsn) {
3844 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3845 if (!isttest(chk->send.http.vsn)) {
3846 memprintf(errmsg, "out of memory");
3847 goto error;
3848 }
3849 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003850 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003851 hdr = calloc(1, sizeof(*hdr));
3852 if (!hdr) {
3853 memprintf(errmsg, "out of memory");
3854 goto error;
3855 }
3856 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003857 hdr->name = istdup(hdrs[i].n);
3858 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003859 memprintf(errmsg, "out of memory");
3860 goto error;
3861 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003862
Christopher Fauletb61caf42020-04-21 10:57:42 +02003863 ist0(hdrs[i].v);
3864 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
Christopher Faulet61cc8522020-04-20 14:54:42 +02003865 goto error;
3866 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3867 hdr = NULL;
3868 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003869
Christopher Faulet61cc8522020-04-20 14:54:42 +02003870 if (body) {
3871 chk->send.http.body = ist2(strdup(body), strlen(body));
3872 if (!isttest(chk->send.http.body)) {
3873 memprintf(errmsg, "out of memory");
3874 goto error;
3875 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003876 }
3877
Christopher Faulet61cc8522020-04-20 14:54:42 +02003878 return chk;
3879
3880 error:
3881 free_tcpcheck_http_hdr(hdr);
3882 free_tcpcheck(chk, 0);
3883 free(comment);
3884 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003885}
3886
Christopher Faulet61cc8522020-04-20 14:54:42 +02003887/* Parses and creates a http-check comment rule. NULL is returned on error */
3888static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3889 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003890{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003891 struct tcpcheck_rule *chk = NULL;
3892 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003893
Christopher Faulet61cc8522020-04-20 14:54:42 +02003894 if (!*(args[cur_arg+1])) {
3895 memprintf(errmsg, "expects a string as argument");
3896 goto error;
3897 }
3898 cur_arg++;
3899 comment = strdup(args[cur_arg]);
3900 if (!comment) {
3901 memprintf(errmsg, "out of memory");
3902 goto error;
3903 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003904
Christopher Faulet61cc8522020-04-20 14:54:42 +02003905 chk = calloc(1, sizeof(*chk));
3906 if (!chk) {
3907 memprintf(errmsg, "out of memory");
3908 goto error;
3909 }
3910 chk->action = TCPCHK_ACT_COMMENT;
3911 chk->comment = comment;
3912 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003913
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914 error:
3915 free(comment);
3916 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003917}
3918
Christopher Faulet61cc8522020-04-20 14:54:42 +02003919/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3920 * on error. <proto> is set to the right protocol flags (covered by the
3921 * TCPCHK_RULES_PROTO_CHK mask).
3922 */
3923static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3924 struct list *rules, unsigned int proto,
3925 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003926{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003927 struct tcpcheck_rule *prev_check, *chk = NULL;
3928 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003929 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003930 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3931 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3932 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3933 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3934 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003935 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003936
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003937 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003938 if (!*(args[cur_arg+1])) {
3939 memprintf(errmsg, "expects at least a matching pattern as arguments");
3940 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003941 }
3942
Christopher Faulet61cc8522020-04-20 14:54:42 +02003943 cur_arg++;
3944 while (*(args[cur_arg])) {
3945 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003946
Christopher Faulet61cc8522020-04-20 14:54:42 +02003947 rescan:
3948 if (strcmp(args[cur_arg], "min-recv") == 0) {
3949 if (in_pattern) {
3950 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3951 goto error;
3952 }
3953 if (!*(args[cur_arg+1])) {
3954 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3955 goto error;
3956 }
3957 /* Use an signed integer here because of chksize */
3958 cur_arg++;
3959 min_recv = atol(args[cur_arg]);
3960 if (min_recv < -1 || min_recv > INT_MAX) {
3961 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3962 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003963 }
3964 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003965 else if (*(args[cur_arg]) == '!') {
3966 in_pattern = 1;
3967 while (*(args[cur_arg]) == '!') {
3968 inverse = !inverse;
3969 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003970 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003971 if (!*(args[cur_arg]))
3972 cur_arg++;
3973 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003974 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003975 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3976 if (type != TCPCHK_EXPECT_UNDEF) {
3977 memprintf(errmsg, "only on pattern expected");
3978 goto error;
3979 }
3980 if (proto != TCPCHK_RULES_HTTP_CHK)
3981 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3982 else
3983 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003984
Christopher Faulet61cc8522020-04-20 14:54:42 +02003985 if (!*(args[cur_arg+1])) {
3986 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3987 goto error;
3988 }
3989 cur_arg++;
3990 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003991 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003992 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3993 if (proto == TCPCHK_RULES_HTTP_CHK)
3994 goto bad_http_kw;
3995 if (type != TCPCHK_EXPECT_UNDEF) {
3996 memprintf(errmsg, "only on pattern expected");
3997 goto error;
3998 }
3999 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004000
Christopher Faulet61cc8522020-04-20 14:54:42 +02004001 if (!*(args[cur_arg+1])) {
4002 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4003 goto error;
4004 }
4005 cur_arg++;
4006 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004007 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004008 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4009 if (proto != TCPCHK_RULES_HTTP_CHK)
4010 goto bad_tcp_kw;
4011 if (type != TCPCHK_EXPECT_UNDEF) {
4012 memprintf(errmsg, "only on pattern expected");
4013 goto error;
4014 }
4015 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004016
Christopher Faulet61cc8522020-04-20 14:54:42 +02004017 if (!*(args[cur_arg+1])) {
4018 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4019 goto error;
4020 }
4021 cur_arg++;
4022 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004023 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004024 else if (strcmp(args[cur_arg], "custom") == 0) {
4025 if (in_pattern) {
4026 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4027 goto error;
4028 }
4029 if (type != TCPCHK_EXPECT_UNDEF) {
4030 memprintf(errmsg, "only on pattern expected");
4031 goto error;
4032 }
4033 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004034 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004035 else if (strcmp(args[cur_arg], "comment") == 0) {
4036 if (in_pattern) {
4037 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4038 goto error;
4039 }
4040 if (!*(args[cur_arg+1])) {
4041 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4042 goto error;
4043 }
4044 cur_arg++;
4045 free(comment);
4046 comment = strdup(args[cur_arg]);
4047 if (!comment) {
4048 memprintf(errmsg, "out of memory");
4049 goto error;
4050 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004051 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004052 else if (strcmp(args[cur_arg], "on-success") == 0) {
4053 if (in_pattern) {
4054 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4055 goto error;
4056 }
4057 if (!*(args[cur_arg+1])) {
4058 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4059 goto error;
4060 }
4061 cur_arg++;
4062 free(on_success_msg);
4063 on_success_msg = strdup(args[cur_arg]);
4064 if (!on_success_msg) {
4065 memprintf(errmsg, "out of memory");
4066 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004067 }
4068 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004069 else if (strcmp(args[cur_arg], "on-error") == 0) {
4070 if (in_pattern) {
4071 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4072 goto error;
4073 }
4074 if (!*(args[cur_arg+1])) {
4075 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4076 goto error;
4077 }
4078 cur_arg++;
4079 free(on_error_msg);
4080 on_error_msg = strdup(args[cur_arg]);
4081 if (!on_error_msg) {
4082 memprintf(errmsg, "out of memory");
4083 goto error;
4084 }
4085 }
4086 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4087 if (in_pattern) {
4088 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4089 goto error;
4090 }
4091 if (!*(args[cur_arg+1])) {
4092 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4093 goto error;
4094 }
4095 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4096 ok_st = HCHK_STATUS_L7OKD;
4097 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4098 ok_st = HCHK_STATUS_L7OKCD;
4099 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4100 ok_st = HCHK_STATUS_L6OK;
4101 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4102 ok_st = HCHK_STATUS_L4OK;
4103 else {
4104 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4105 args[cur_arg], args[cur_arg+1]);
4106 goto error;
4107 }
4108 cur_arg++;
4109 }
4110 else if (strcmp(args[cur_arg], "error-status") == 0) {
4111 if (in_pattern) {
4112 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4113 goto error;
4114 }
4115 if (!*(args[cur_arg+1])) {
4116 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4117 goto error;
4118 }
4119 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4120 err_st = HCHK_STATUS_L7RSP;
4121 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4122 err_st = HCHK_STATUS_L7STS;
4123 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4124 err_st = HCHK_STATUS_L6RSP;
4125 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4126 err_st = HCHK_STATUS_L4CON;
4127 else {
4128 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4129 args[cur_arg], args[cur_arg+1]);
4130 goto error;
4131 }
4132 cur_arg++;
4133 }
4134 else if (strcmp(args[cur_arg], "status-code") == 0) {
4135 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004136
Christopher Faulet61cc8522020-04-20 14:54:42 +02004137 if (in_pattern) {
4138 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4139 goto error;
4140 }
4141 if (!*(args[cur_arg+1])) {
4142 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4143 goto error;
4144 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004145
Christopher Faulet61cc8522020-04-20 14:54:42 +02004146 cur_arg++;
4147 release_sample_expr(status_expr);
4148 px->conf.args.ctx = ARGC_SRV;
4149 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4150 file, line, errmsg, &px->conf.args, NULL);
4151 if (!status_expr) {
4152 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4153 goto error;
4154 }
4155 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4156 memprintf(errmsg, "error detected while parsing status-code expression : "
4157 " fetch method '%s' extracts information from '%s', "
4158 "none of which is available here.\n",
4159 args[cur_arg], sample_src_names(status_expr->fetch->use));
4160 goto error;
4161 }
4162 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4163 }
4164 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4165 if (in_pattern) {
4166 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4167 goto error;
4168 }
4169 if (!*(args[cur_arg+1])) {
4170 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4171 goto error;
4172 }
4173 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4174 tout_st = HCHK_STATUS_L7TOUT;
4175 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4176 tout_st = HCHK_STATUS_L6TOUT;
4177 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4178 tout_st = HCHK_STATUS_L4TOUT;
4179 else {
4180 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4181 args[cur_arg], args[cur_arg+1]);
4182 goto error;
4183 }
4184 cur_arg++;
4185 }
4186 else {
4187 if (proto == TCPCHK_RULES_HTTP_CHK) {
4188 bad_http_kw:
4189 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4190 " or comment but got '%s' as argument.", args[cur_arg]);
4191 }
4192 else {
4193 bad_tcp_kw:
4194 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4195 " or comment but got '%s' as argument.", args[cur_arg]);
4196 }
4197 goto error;
4198 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004199
Christopher Faulet61cc8522020-04-20 14:54:42 +02004200 cur_arg++;
4201 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004202
Christopher Faulet61cc8522020-04-20 14:54:42 +02004203 chk = calloc(1, sizeof(*chk));
4204 if (!chk) {
4205 memprintf(errmsg, "out of memory");
4206 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004207 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004208 chk->action = TCPCHK_ACT_EXPECT;
4209 LIST_INIT(&chk->expect.onerror_fmt);
4210 LIST_INIT(&chk->expect.onsuccess_fmt);
4211 chk->comment = comment; comment = NULL;
4212 chk->expect.type = type;
4213 chk->expect.min_recv = min_recv;
4214 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004215 chk->expect.ok_status = ok_st;
4216 chk->expect.err_status = err_st;
4217 chk->expect.tout_status = tout_st;
4218 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004219
Christopher Faulet61cc8522020-04-20 14:54:42 +02004220 if (on_success_msg) {
4221 px->conf.args.ctx = ARGC_SRV;
4222 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4223 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4224 goto error;
4225 }
4226 free(on_success_msg);
4227 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004228 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004229 if (on_error_msg) {
4230 px->conf.args.ctx = ARGC_SRV;
4231 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4232 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4233 goto error;
4234 }
4235 free(on_error_msg);
4236 on_error_msg = NULL;
4237 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004238
Christopher Faulet61cc8522020-04-20 14:54:42 +02004239 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004240 case TCPCHK_EXPECT_HTTP_STATUS: {
4241 const char *p = pattern;
4242 unsigned int c1,c2;
4243
4244 chk->expect.codes.codes = NULL;
4245 chk->expect.codes.num = 0;
4246 while (1) {
4247 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4248 if (*p == '-') {
4249 p++;
4250 c2 = read_uint(&p, pattern + strlen(pattern));
4251 }
4252 if (c1 > c2) {
4253 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4254 goto error;
4255 }
4256
4257 chk->expect.codes.num++;
4258 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4259 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4260 if (!chk->expect.codes.codes) {
4261 memprintf(errmsg, "out of memory");
4262 goto error;
4263 }
4264 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4265 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4266
4267 if (*p == '\0')
4268 break;
4269 if (*p != ',') {
4270 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4271 goto error;
4272 }
4273 p++;
4274 }
4275 break;
4276 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004277 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004278 case TCPCHK_EXPECT_HTTP_BODY:
4279 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004280 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004281 memprintf(errmsg, "out of memory");
4282 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004283 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004284 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004285 case TCPCHK_EXPECT_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02004286 int len = chk->expect.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004287
4288 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004289 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4290 goto error;
4291 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004292 chk->expect.data.len = len;
4293 break;
4294 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004295 case TCPCHK_EXPECT_REGEX:
4296 case TCPCHK_EXPECT_REGEX_BINARY:
4297 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4298 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004299 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004300 if (!chk->expect.regex)
4301 goto error;
4302 break;
4303 case TCPCHK_EXPECT_CUSTOM:
4304 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4305 break;
4306 case TCPCHK_EXPECT_UNDEF:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004307 memprintf(errmsg, "pattern not found");
4308 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004309 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004310
Christopher Faulet61cc8522020-04-20 14:54:42 +02004311 /* All tcp-check expect points back to the first inverse expect rule in
4312 * a chain of one or more expect rule, potentially itself.
4313 */
4314 chk->expect.head = chk;
4315 list_for_each_entry_rev(prev_check, rules, list) {
4316 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4317 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4318 chk->expect.head = prev_check;
4319 continue;
4320 }
4321 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4322 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004323 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004324 return chk;
4325
4326 error:
4327 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004328 free(comment);
4329 free(on_success_msg);
4330 free(on_error_msg);
4331 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004332 return NULL;
4333}
4334
Christopher Faulet61cc8522020-04-20 14:54:42 +02004335/* Overwrites fields of the old http send rule with those of the new one. When
4336 * replaced, old values are freed and replaced by the new ones. New values are
4337 * not copied but transferred. At the end <new> should be empty and can be
4338 * safely released. This function never fails.
4339 */
4340static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004341{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004342 struct logformat_node *lf, *lfb;
4343 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004344
Christopher Faulet404f9192020-04-09 23:13:54 +02004345
Christopher Faulet61cc8522020-04-20 14:54:42 +02004346 if (new->send.http.meth.str.area) {
4347 free(old->send.http.meth.str.area);
4348 old->send.http.meth.meth = new->send.http.meth.meth;
4349 old->send.http.meth.str.area = new->send.http.meth.str.area;
4350 old->send.http.meth.str.data = new->send.http.meth.str.data;
4351 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004352 }
4353
Christopher Faulet61cc8522020-04-20 14:54:42 +02004354 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4355 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004356 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004357 else
4358 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4359 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4360 old->send.http.uri = new->send.http.uri;
4361 new->send.http.uri = IST_NULL;
4362 }
4363 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4364 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004365 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004366 else
4367 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4368 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4369 LIST_INIT(&old->send.http.uri_fmt);
4370 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4371 LIST_DEL(&lf->list);
4372 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4373 }
4374 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004375
Christopher Faulet61cc8522020-04-20 14:54:42 +02004376 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004377 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004378 old->send.http.vsn = new->send.http.vsn;
4379 new->send.http.vsn = IST_NULL;
4380 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004381
Christopher Faulet61cc8522020-04-20 14:54:42 +02004382 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4383 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4384 LIST_DEL(&hdr->list);
4385 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004386 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004387
4388 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4389 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004390 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004391 else
4392 free_tcpcheck_fmt(&old->send.http.body_fmt);
4393 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4394 old->send.http.body = new->send.http.body;
4395 new->send.http.body = IST_NULL;
4396 }
4397 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4398 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004399 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004400 else
4401 free_tcpcheck_fmt(&old->send.http.body_fmt);
4402 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4403 LIST_INIT(&old->send.http.body_fmt);
4404 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4405 LIST_DEL(&lf->list);
4406 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4407 }
4408 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004409}
4410
Christopher Faulet61cc8522020-04-20 14:54:42 +02004411/* Internal function used to add an http-check rule in a list during the config
4412 * parsing step. Depending on its type, and the previously inserted rules, a
4413 * specific action may be performed or an error may be reported. This functions
4414 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4415 * message.
4416 */
4417static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004418{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004419 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004420
Christopher Faulet61cc8522020-04-20 14:54:42 +02004421 /* the implicit send rule coming from an "option httpchk" line must be
4422 * merged with the first explici http-check send rule, if
4423 * any. Depdending the declaration order some tests are required.
4424 *
4425 * Some tests is also required for other kinds of http-check rules to be
4426 * sure the ruleset remains valid.
4427 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004428
Christopher Faulet61cc8522020-04-20 14:54:42 +02004429 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4430 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4431 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4432 * following tests are performed :
4433 *
4434 * 1- If there is no such rule or if it is not a send rule, the implicit send
4435 * rule is pushed in front of the ruleset
4436 *
4437 * 2- If it is another implicit send rule, it is replaced with the new one.
4438 *
4439 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4440 * both, overwritting the old send rule (the explicit one) with info of the
4441 * new send rule (the implicit one).
4442 */
4443 r = get_first_tcpcheck_rule(rules);
4444 if (r && r->action == TCPCHK_ACT_CONNECT)
4445 r = get_next_tcpcheck_rule(rules, r);
4446 if (!r || r->action != TCPCHK_ACT_SEND)
4447 LIST_ADD(rules->list, &chk->list);
4448 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4449 LIST_DEL(&r->list);
4450 free_tcpcheck(r, 0);
4451 LIST_ADD(rules->list, &chk->list);
4452 }
4453 else {
4454 tcpcheck_overwrite_send_http_rule(r, chk);
4455 free_tcpcheck(chk, 0);
4456 }
4457 }
4458 else {
4459 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4460 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4461 * with an existing implicit send rule, if any. At the end, if there is no error,
4462 * the rule is appended to the list.
4463 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004464
Christopher Faulet61cc8522020-04-20 14:54:42 +02004465 r = get_last_tcpcheck_rule(rules);
4466 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4467 /* no error */;
4468 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4469 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4470 chk->index+1);
4471 return 0;
4472 }
4473 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4474 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4475 chk->index+1);
4476 return 0;
4477 }
4478 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4479 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4480 chk->index+1);
4481 return 0;
4482 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004483
Christopher Faulet61cc8522020-04-20 14:54:42 +02004484 if (chk->action == TCPCHK_ACT_SEND) {
4485 r = get_first_tcpcheck_rule(rules);
4486 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4487 tcpcheck_overwrite_send_http_rule(r, chk);
4488 free_tcpcheck(chk, 0);
4489 LIST_DEL(&r->list);
4490 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4491 chk = r;
4492 }
4493 }
4494 LIST_ADDQ(rules->list, &chk->list);
4495 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004496 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004497}
4498
Christopher Faulet61cc8522020-04-20 14:54:42 +02004499/**************************************************************************/
4500/************************** Init/deinit checks ****************************/
4501/**************************************************************************/
4502static const char *init_check(struct check *check, int type)
4503{
4504 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004505
Christopher Faulet61cc8522020-04-20 14:54:42 +02004506 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4507 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004508
Christopher Faulet61cc8522020-04-20 14:54:42 +02004509 check->bi.area = calloc(check->bi.size, sizeof(char));
4510 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004511
Christopher Faulet61cc8522020-04-20 14:54:42 +02004512 if (!check->bi.area || !check->bo.area)
4513 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004514
Christopher Faulet61cc8522020-04-20 14:54:42 +02004515 check->wait_list.tasklet = tasklet_new();
4516 if (!check->wait_list.tasklet)
4517 return "out of memory while allocating check tasklet";
4518 check->wait_list.events = 0;
4519 check->wait_list.tasklet->process = event_srv_chk_io;
4520 check->wait_list.tasklet->context = check;
4521 return NULL;
4522}
4523
4524void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004525{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004526 task_destroy(check->task);
4527 if (check->wait_list.tasklet)
4528 tasklet_free(check->wait_list.tasklet);
4529
4530 free(check->bi.area);
4531 free(check->bo.area);
4532 if (check->cs) {
4533 free(check->cs->conn);
4534 check->cs->conn = NULL;
4535 cs_free(check->cs);
4536 check->cs = NULL;
4537 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004538}
4539
Christopher Faulet61cc8522020-04-20 14:54:42 +02004540/* manages a server health-check. Returns the time the task accepts to wait, or
4541 * TIME_ETERNITY for infinity.
4542 */
4543static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004544{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004545 struct check *check = context;
4546
4547 if (check->type == PR_O2_EXT_CHK)
4548 return process_chk_proc(t, context, state);
4549 return process_chk_conn(t, context, state);
4550
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004551}
4552
Christopher Faulet61cc8522020-04-20 14:54:42 +02004553
4554static int start_check_task(struct check *check, int mininter,
4555 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004556{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004557 struct task *t;
4558 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004559
Christopher Faulet61cc8522020-04-20 14:54:42 +02004560 if (check->type == PR_O2_EXT_CHK)
4561 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004562
Christopher Faulet61cc8522020-04-20 14:54:42 +02004563 /* task for the check */
4564 if ((t = task_new(thread_mask)) == NULL) {
4565 ha_alert("Starting [%s:%s] check: out of memory.\n",
4566 check->server->proxy->id, check->server->id);
4567 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004568 }
4569
Christopher Faulet61cc8522020-04-20 14:54:42 +02004570 check->task = t;
4571 t->process = process_chk;
4572 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004573
Christopher Faulet61cc8522020-04-20 14:54:42 +02004574 if (mininter < srv_getinter(check))
4575 mininter = srv_getinter(check);
4576
4577 if (global.max_spread_checks && mininter > global.max_spread_checks)
4578 mininter = global.max_spread_checks;
4579
4580 /* check this every ms */
4581 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4582 check->start = now;
4583 task_queue(t);
4584
4585 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004586}
4587
Christopher Faulet61cc8522020-04-20 14:54:42 +02004588/* updates the server's weight during a warmup stage. Once the final weight is
4589 * reached, the task automatically stops. Note that any server status change
4590 * must have updated s->last_change accordingly.
4591 */
4592static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004593{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004594 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004595
Christopher Faulet61cc8522020-04-20 14:54:42 +02004596 /* by default, plan on stopping the task */
4597 t->expire = TICK_ETERNITY;
4598 if ((s->next_admin & SRV_ADMF_MAINT) ||
4599 (s->next_state != SRV_ST_STARTING))
4600 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004601
Christopher Faulet61cc8522020-04-20 14:54:42 +02004602 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004603
Christopher Faulet61cc8522020-04-20 14:54:42 +02004604 /* recalculate the weights and update the state */
4605 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004606
Christopher Faulet61cc8522020-04-20 14:54:42 +02004607 /* probably that we can refill this server with a bit more connections */
4608 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004609
Christopher Faulet61cc8522020-04-20 14:54:42 +02004610 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004611
Christopher Faulet61cc8522020-04-20 14:54:42 +02004612 /* get back there in 1 second or 1/20th of the slowstart interval,
4613 * whichever is greater, resulting in small 5% steps.
4614 */
4615 if (s->next_state == SRV_ST_STARTING)
4616 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4617 return t;
4618}
4619
4620/*
4621 * Start health-check.
4622 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4623 */
4624static int start_checks()
4625{
4626
4627 struct proxy *px;
4628 struct server *s;
4629 struct task *t;
4630 int nbcheck=0, mininter=0, srvpos=0;
4631
4632 /* 0- init the dummy frontend used to create all checks sessions */
4633 init_new_proxy(&checks_fe);
4634 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4635 checks_fe.mode = PR_MODE_TCP;
4636 checks_fe.maxconn = 0;
4637 checks_fe.conn_retries = CONN_RETRIES;
4638 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4639 checks_fe.timeout.client = TICK_ETERNITY;
4640
4641 /* 1- count the checkers to run simultaneously.
4642 * We also determine the minimum interval among all of those which
4643 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4644 * will be used to spread their start-up date. Those which have
4645 * a shorter interval will start independently and will not dictate
4646 * too short an interval for all others.
4647 */
4648 for (px = proxies_list; px; px = px->next) {
4649 for (s = px->srv; s; s = s->next) {
4650 if (s->slowstart) {
4651 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4652 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4653 return ERR_ALERT | ERR_FATAL;
4654 }
4655 /* We need a warmup task that will be called when the server
4656 * state switches from down to up.
4657 */
4658 s->warmup = t;
4659 t->process = server_warmup;
4660 t->context = s;
4661 /* server can be in this state only because of */
4662 if (s->next_state == SRV_ST_STARTING)
4663 task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, (now.tv_sec - s->last_change)) / 20)));
Christopher Faulet5c288742020-03-31 08:15:58 +02004664 }
4665
Christopher Faulet61cc8522020-04-20 14:54:42 +02004666 if (s->check.state & CHK_ST_CONFIGURED) {
4667 nbcheck++;
4668 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4669 (!mininter || mininter > srv_getinter(&s->check)))
4670 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004671 }
4672
Christopher Faulet61cc8522020-04-20 14:54:42 +02004673 if (s->agent.state & CHK_ST_CONFIGURED) {
4674 nbcheck++;
4675 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4676 (!mininter || mininter > srv_getinter(&s->agent)))
4677 mininter = srv_getinter(&s->agent);
4678 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004679 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004680 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004681
Christopher Faulet61cc8522020-04-20 14:54:42 +02004682 if (!nbcheck)
4683 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004684
Christopher Faulet61cc8522020-04-20 14:54:42 +02004685 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004686
Christopher Faulet61cc8522020-04-20 14:54:42 +02004687 /*
4688 * 2- start them as far as possible from each others. For this, we will
4689 * start them after their interval set to the min interval divided by
4690 * the number of servers, weighted by the server's position in the list.
4691 */
4692 for (px = proxies_list; px; px = px->next) {
4693 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4694 if (init_pid_list()) {
4695 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4696 return ERR_ALERT | ERR_FATAL;
4697 }
4698 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004699
Christopher Faulet61cc8522020-04-20 14:54:42 +02004700 for (s = px->srv; s; s = s->next) {
4701 /* A task for the main check */
4702 if (s->check.state & CHK_ST_CONFIGURED) {
4703 if (s->check.type == PR_O2_EXT_CHK) {
4704 if (!prepare_external_check(&s->check))
4705 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004706 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004707 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4708 return ERR_ALERT | ERR_FATAL;
4709 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004710 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004711
Christopher Faulet61cc8522020-04-20 14:54:42 +02004712 /* A task for a auxiliary agent check */
4713 if (s->agent.state & CHK_ST_CONFIGURED) {
4714 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4715 return ERR_ALERT | ERR_FATAL;
4716 }
4717 srvpos++;
4718 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004719 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004720 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004721 return 0;
4722}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004723
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004724
Christopher Faulet61cc8522020-04-20 14:54:42 +02004725/*
4726 * Return value:
4727 * the port to be used for the health check
4728 * 0 in case no port could be found for the check
4729 */
4730static int srv_check_healthcheck_port(struct check *chk)
4731{
4732 int i = 0;
4733 struct server *srv = NULL;
4734
4735 srv = chk->server;
4736
4737 /* by default, we use the health check port ocnfigured */
4738 if (chk->port > 0)
4739 return chk->port;
4740
4741 /* try to get the port from check_core.addr if check.port not set */
4742 i = get_host_port(&chk->addr);
4743 if (i > 0)
4744 return i;
4745
4746 /* try to get the port from server address */
4747 /* prevent MAPPORTS from working at this point, since checks could
4748 * not be performed in such case (MAPPORTS impose a relative ports
4749 * based on live traffic)
4750 */
4751 if (srv->flags & SRV_F_MAPPORTS)
4752 return 0;
4753
4754 i = srv->svc_port; /* by default */
4755 if (i > 0)
4756 return i;
4757
4758 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004759}
4760
Christopher Faulet61cc8522020-04-20 14:54:42 +02004761/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4762 * if an error occurred.
4763 */
4764static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004765{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004766 const char *err;
4767 struct tcpcheck_rule *r;
4768 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004769
Christopher Faulet61cc8522020-04-20 14:54:42 +02004770 if (!srv->do_check)
4771 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004772
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004773
Christopher Faulet61cc8522020-04-20 14:54:42 +02004774 /* If neither a port nor an addr was specified and no check transport
4775 * layer is forced, then the transport layer used by the checks is the
4776 * same as for the production traffic. Otherwise we use raw_sock by
4777 * default, unless one is specified.
4778 */
4779 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4780 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4781 srv->check.use_ssl = srv->use_ssl;
4782 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004783 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004784 else if (srv->check.use_ssl == 1)
4785 srv->check.xprt = xprt_get(XPRT_SSL);
4786 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004787 }
4788
Christopher Faulet12882cf2020-04-23 15:50:18 +02004789 /* Inherit the mux protocol from the server if not already defined for
4790 * the check
4791 */
4792 if (srv->mux_proto && !srv->check.mux_proto)
4793 srv->check.mux_proto = srv->mux_proto;
4794
Christopher Faulet61cc8522020-04-20 14:54:42 +02004795 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004796
Christopher Faulet61cc8522020-04-20 14:54:42 +02004797 /* We need at least a service port, a check port or the first tcp-check
4798 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4799 */
4800 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4801 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4802 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004803
Christopher Faulet61cc8522020-04-20 14:54:42 +02004804 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4805 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4806 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4807 ret |= ERR_ALERT | ERR_ABORT;
4808 goto out;
4809 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004810
Christopher Faulet61cc8522020-04-20 14:54:42 +02004811 /* search the first action (connect / send / expect) in the list */
4812 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4813 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4814 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4815 "nor tcp_check rule 'connect' with port information.\n",
4816 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4817 ret |= ERR_ALERT | ERR_ABORT;
4818 goto out;
4819 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004820
Christopher Faulet61cc8522020-04-20 14:54:42 +02004821 /* scan the tcp-check ruleset to ensure a port has been configured */
4822 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4823 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4824 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4825 "and a tcp_check rule 'connect' with no port information.\n",
4826 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4827 ret |= ERR_ALERT | ERR_ABORT;
4828 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004829 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004830 }
4831
Christopher Faulet61cc8522020-04-20 14:54:42 +02004832 init:
4833 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4834 struct tcpcheck_ruleset *rs = NULL;
4835 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4836 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004837
Christopher Faulet61cc8522020-04-20 14:54:42 +02004838 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4839 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004840
Christopher Faulet61cc8522020-04-20 14:54:42 +02004841 rs = find_tcpcheck_ruleset("*tcp-check");
4842 if (!rs) {
4843 rs = create_tcpcheck_ruleset("*tcp-check");
4844 if (rs == NULL) {
4845 ha_alert("config: %s '%s': out of memory.\n",
4846 proxy_type_str(srv->proxy), srv->proxy->id);
4847 ret |= ERR_ALERT | ERR_FATAL;
4848 goto out;
4849 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004850 }
4851
Christopher Faulet61cc8522020-04-20 14:54:42 +02004852 free_tcpcheck_vars(&rules->preset_vars);
4853 rules->list = &rs->rules;
4854 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004855 }
4856
Christopher Faulet61cc8522020-04-20 14:54:42 +02004857 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4858 if (err) {
4859 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4860 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4861 ret |= ERR_ALERT | ERR_ABORT;
4862 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004863 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004864 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4865 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004866
Christopher Faulet61cc8522020-04-20 14:54:42 +02004867 out:
4868 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004869}
4870
Christopher Faulet61cc8522020-04-20 14:54:42 +02004871/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4872 * if an error occurred.
4873 */
4874static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004875{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004876 struct tcpcheck_rule *chk;
4877 const char *err;
4878 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004879
Christopher Faulet61cc8522020-04-20 14:54:42 +02004880 if (!srv->do_agent)
4881 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004882
Christopher Faulet61cc8522020-04-20 14:54:42 +02004883 /* If there is no connect rule preceeding all send / expect rules, an
4884 * implicit one is inserted before all others.
4885 */
4886 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4887 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4888 chk = calloc(1, sizeof(*chk));
4889 if (!chk) {
4890 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4891 " to agent-check for server '%s' (out of memory).\n",
4892 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4893 ret |= ERR_ALERT | ERR_FATAL;
4894 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004895 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004896 chk->action = TCPCHK_ACT_CONNECT;
4897 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4898 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004899 }
4900
Christopher Faulete5870d82020-04-15 11:32:03 +02004901
Christopher Faulet61cc8522020-04-20 14:54:42 +02004902 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4903 if (err) {
4904 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4905 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4906 ret |= ERR_ALERT | ERR_ABORT;
4907 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004908 }
4909
Christopher Faulet61cc8522020-04-20 14:54:42 +02004910 if (!srv->agent.inter)
4911 srv->agent.inter = srv->check.inter;
4912
4913 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4914 global.maxsock++;
4915
4916 out:
4917 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004918}
4919
Christopher Faulet61cc8522020-04-20 14:54:42 +02004920/* Check tcp-check health-check configuration for the proxy <px>. */
4921static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004922{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004923 struct tcpcheck_rule *chk, *back;
4924 char *comment = NULL, *errmsg = NULL;
4925 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4926 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004927
Christopher Faulet61cc8522020-04-20 14:54:42 +02004928 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4929 deinit_proxy_tcpcheck(px);
4930 goto out;
4931 }
4932
4933 free(px->check_command);
4934 free(px->check_path);
4935 px->check_command = px->check_path = NULL;
4936
4937 if (!px->tcpcheck_rules.list) {
4938 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4939 ret |= ERR_ALERT | ERR_FATAL;
4940 goto out;
4941 }
4942
4943 /* HTTP ruleset only : */
4944 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4945 struct tcpcheck_rule *next;
4946
4947 /* move remaining implicit send rule from "option httpchk" line to the right place.
4948 * If such rule exists, it must be the first one. In this case, the rule is moved
4949 * after the first connect rule, if any. Otherwise, nothing is done.
4950 */
4951 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4952 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4953 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4954 if (next && next->action == TCPCHK_ACT_CONNECT) {
4955 LIST_DEL(&chk->list);
4956 LIST_ADD(&next->list, &chk->list);
4957 chk->index = next->index;
4958 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004959 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004960
4961 /* add implicit expect rule if the last one is a send. It is inherited from previous
4962 * versions where the http expect rule was optional. Now it is possible to chained
4963 * send/expect rules but the last expect may still be implicit.
4964 */
4965 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4966 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004967 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004968 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4969 px->conf.file, px->conf.line, &errmsg);
4970 if (!next) {
4971 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4972 "(%s).\n", px->id, errmsg);
4973 free(errmsg);
4974 ret |= ERR_ALERT | ERR_FATAL;
4975 goto out;
4976 }
4977 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4978 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004979 }
4980 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004981
4982 /* For all ruleset: */
4983
4984 /* If there is no connect rule preceeding all send / expect rules, an
4985 * implicit one is inserted before all others.
4986 */
4987 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4988 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4989 chk = calloc(1, sizeof(*chk));
4990 if (!chk) {
4991 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4992 "(out of memory).\n", px->id);
4993 ret |= ERR_ALERT | ERR_FATAL;
4994 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004995 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004996 chk->action = TCPCHK_ACT_CONNECT;
4997 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4998 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4999 }
5000
5001 /* Remove all comment rules. To do so, when a such rule is found, the
5002 * comment is assigned to the following rule(s).
5003 */
5004 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5005 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5006 free(comment);
5007 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005008 }
5009
Christopher Faulet61cc8522020-04-20 14:54:42 +02005010 prev_action = chk->action;
5011 switch (chk->action) {
5012 case TCPCHK_ACT_COMMENT:
5013 free(comment);
5014 comment = chk->comment;
5015 LIST_DEL(&chk->list);
5016 free(chk);
5017 break;
5018 case TCPCHK_ACT_CONNECT:
5019 if (!chk->comment && comment)
5020 chk->comment = strdup(comment);
5021 /* fall though */
5022 case TCPCHK_ACT_ACTION_KW:
5023 free(comment);
5024 comment = NULL;
5025 break;
5026 case TCPCHK_ACT_SEND:
5027 case TCPCHK_ACT_EXPECT:
5028 if (!chk->comment && comment)
5029 chk->comment = strdup(comment);
5030 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005031 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005032 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033 free(comment);
5034 comment = NULL;
5035
5036 out:
5037 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005038}
5039
Christopher Faulet61cc8522020-04-20 14:54:42 +02005040void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005041{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005042 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5043 px->tcpcheck_rules.flags = 0;
5044 px->tcpcheck_rules.list = NULL;
5045}
Christopher Faulete5870d82020-04-15 11:32:03 +02005046
Christopher Faulet61cc8522020-04-20 14:54:42 +02005047static void deinit_srv_check(struct server *srv)
5048{
5049 if (srv->check.state & CHK_ST_CONFIGURED)
5050 free_check(&srv->check);
5051 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5052 srv->do_check = 0;
5053}
Christopher Faulete5870d82020-04-15 11:32:03 +02005054
Christopher Faulet61cc8522020-04-20 14:54:42 +02005055
5056static void deinit_srv_agent_check(struct server *srv)
5057{
5058 if (srv->agent.tcpcheck_rules) {
5059 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5060 free(srv->agent.tcpcheck_rules);
5061 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005062 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005063
Christopher Faulet61cc8522020-04-20 14:54:42 +02005064 if (srv->agent.state & CHK_ST_CONFIGURED)
5065 free_check(&srv->agent);
5066
5067 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5068 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005069}
5070
Christopher Faulet61cc8522020-04-20 14:54:42 +02005071static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005072{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005073 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005074 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005075 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005076
Christopher Fauletd7cee712020-04-21 13:45:00 +02005077 node = ebpt_first(&shared_tcpchecks);
5078 while (node) {
5079 next = ebpt_next(node);
5080 ebpt_delete(node);
5081 free(node->key);
5082 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005083 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5084 LIST_DEL(&r->list);
5085 free_tcpcheck(r, 0);
5086 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005087 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005088 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005089 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005090}
Christopher Faulete5870d82020-04-15 11:32:03 +02005091
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005092
Christopher Faulet61cc8522020-04-20 14:54:42 +02005093REGISTER_POST_SERVER_CHECK(init_srv_check);
5094REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5095REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5096REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005097
Christopher Faulet61cc8522020-04-20 14:54:42 +02005098REGISTER_SERVER_DEINIT(deinit_srv_check);
5099REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5100REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5101REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005102
Christopher Faulet61cc8522020-04-20 14:54:42 +02005103/**************************************************************************/
5104/****************************** Email alerts ******************************/
5105/* NOTE: It may be pertinent to use an applet to handle email alerts */
5106/* instead of a tcp-check ruleset */
5107/**************************************************************************/
5108void email_alert_free(struct email_alert *alert)
5109{
5110 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005111
Christopher Faulet61cc8522020-04-20 14:54:42 +02005112 if (!alert)
5113 return;
5114
5115 if (alert->rules.list) {
5116 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5117 LIST_DEL(&rule->list);
5118 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005119 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005120 free_tcpcheck_vars(&alert->rules.preset_vars);
5121 free(alert->rules.list);
5122 alert->rules.list = NULL;
5123 }
5124 pool_free(pool_head_email_alert, alert);
5125}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005126
Christopher Faulet61cc8522020-04-20 14:54:42 +02005127static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5128{
5129 struct check *check = context;
5130 struct email_alertq *q;
5131 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005132
Christopher Faulet61cc8522020-04-20 14:54:42 +02005133 q = container_of(check, typeof(*q), check);
5134
5135 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5136 while (1) {
5137 if (!(check->state & CHK_ST_ENABLED)) {
5138 if (LIST_ISEMPTY(&q->email_alerts)) {
5139 /* All alerts processed, queue the task */
5140 t->expire = TICK_ETERNITY;
5141 task_queue(t);
5142 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005143 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005144
5145 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5146 LIST_DEL(&alert->list);
5147 t->expire = now_ms;
5148 check->tcpcheck_rules = &alert->rules;
5149 check->status = HCHK_STATUS_INI;
5150 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005151 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005152
5153 process_chk(t, context, state);
5154 if (check->state & CHK_ST_INPROGRESS)
5155 break;
5156
5157 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5158 email_alert_free(alert);
5159 check->tcpcheck_rules = NULL;
5160 check->server = NULL;
5161 check->state &= ~CHK_ST_ENABLED;
5162 }
5163 end:
5164 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5165 return t;
5166}
5167
5168/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5169 *
5170 * The function returns 1 in success case, otherwise, it returns 0 and err is
5171 * filled.
5172 */
5173int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5174{
5175 struct mailer *mailer;
5176 struct email_alertq *queues;
5177 const char *err_str;
5178 int i = 0;
5179
5180 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5181 memprintf(err, "out of memory while allocating mailer alerts queues");
5182 goto fail_no_queue;
5183 }
5184
5185 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5186 struct email_alertq *q = &queues[i];
5187 struct check *check = &q->check;
5188 struct task *t;
5189
5190 LIST_INIT(&q->email_alerts);
5191 HA_SPIN_INIT(&q->lock);
5192 check->inter = mls->timeout.mail;
5193 check->rise = DEF_AGENT_RISETIME;
5194 check->proxy = p;
5195 check->fall = DEF_AGENT_FALLTIME;
5196 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5197 memprintf(err, "%s", err_str);
5198 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005199 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005200
5201 check->xprt = mailer->xprt;
5202 check->addr = mailer->addr;
5203 check->port = get_host_port(&mailer->addr);
5204
5205 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5206 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005207 goto error;
5208 }
5209
Christopher Faulet61cc8522020-04-20 14:54:42 +02005210 check->task = t;
5211 t->process = process_email_alert;
5212 t->context = check;
5213
5214 /* check this in one ms */
5215 t->expire = TICK_ETERNITY;
5216 check->start = now;
5217 task_queue(t);
5218 }
5219
5220 mls->users++;
5221 free(p->email_alert.mailers.name);
5222 p->email_alert.mailers.m = mls;
5223 p->email_alert.queues = queues;
5224 return 0;
5225
5226 error:
5227 for (i = 0; i < mls->count; i++) {
5228 struct email_alertq *q = &queues[i];
5229 struct check *check = &q->check;
5230
5231 free_check(check);
5232 }
5233 free(queues);
5234 fail_no_queue:
5235 return 1;
5236}
5237
5238static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5239{
5240 struct tcpcheck_rule *tcpcheck, *prev_check;
5241 struct tcpcheck_expect *expect;
5242
5243 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5244 return 0;
5245 memset(tcpcheck, 0, sizeof(*tcpcheck));
5246 tcpcheck->action = TCPCHK_ACT_EXPECT;
5247
5248 expect = &tcpcheck->expect;
5249 expect->type = TCPCHK_EXPECT_STRING;
5250 LIST_INIT(&expect->onerror_fmt);
5251 LIST_INIT(&expect->onsuccess_fmt);
5252 expect->ok_status = HCHK_STATUS_L7OKD;
5253 expect->err_status = HCHK_STATUS_L7RSP;
5254 expect->tout_status = HCHK_STATUS_L7TOUT;
5255 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005256 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005257 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5258 return 0;
5259 }
5260
5261 /* All tcp-check expect points back to the first inverse expect rule
5262 * in a chain of one or more expect rule, potentially itself.
5263 */
5264 tcpcheck->expect.head = tcpcheck;
5265 list_for_each_entry_rev(prev_check, rules->list, list) {
5266 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5267 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5268 tcpcheck->expect.head = prev_check;
5269 continue;
5270 }
5271 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5272 break;
5273 }
5274 LIST_ADDQ(rules->list, &tcpcheck->list);
5275 return 1;
5276}
5277
5278static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5279{
5280 struct tcpcheck_rule *tcpcheck;
5281 struct tcpcheck_send *send;
5282 const char *in;
5283 char *dst;
5284 int i;
5285
5286 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5287 return 0;
5288 memset(tcpcheck, 0, sizeof(*tcpcheck));
5289 tcpcheck->action = TCPCHK_ACT_SEND;
5290
5291 send = &tcpcheck->send;
5292 send->type = TCPCHK_SEND_STRING;
5293
5294 for (i = 0; strs[i]; i++)
5295 send->data.len += strlen(strs[i]);
5296
Christopher Fauletb61caf42020-04-21 10:57:42 +02005297 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005298 if (!isttest(send->data)) {
5299 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5300 return 0;
5301 }
5302
Christopher Fauletb61caf42020-04-21 10:57:42 +02005303 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005304 for (i = 0; strs[i]; i++)
5305 for (in = strs[i]; (*dst = *in++); dst++);
5306 *dst = 0;
5307
5308 LIST_ADDQ(rules->list, &tcpcheck->list);
5309 return 1;
5310}
5311
5312static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5313 struct email_alertq *q, const char *msg)
5314{
5315 struct email_alert *alert;
5316 struct tcpcheck_rule *tcpcheck;
5317 struct check *check = &q->check;
5318
5319 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5320 goto error;
5321 LIST_INIT(&alert->list);
5322 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5323 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5324 if (!alert->rules.list)
5325 goto error;
5326 LIST_INIT(alert->rules.list);
5327 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5328 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005329
Christopher Faulet61cc8522020-04-20 14:54:42 +02005330 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5331 goto error;
5332 memset(tcpcheck, 0, sizeof(*tcpcheck));
5333 tcpcheck->action = TCPCHK_ACT_CONNECT;
5334 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005335
Christopher Faulet61cc8522020-04-20 14:54:42 +02005336 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005337
Christopher Faulet61cc8522020-04-20 14:54:42 +02005338 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005339 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005340
5341 {
5342 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5343 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5344 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005345 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005346
Christopher Faulet61cc8522020-04-20 14:54:42 +02005347 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5348 goto error;
5349
5350 {
5351 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5352 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005353 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005354 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005355
5356 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5357 goto error;
5358
5359 {
5360 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5361 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005362 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005363 }
5364
Christopher Faulet61cc8522020-04-20 14:54:42 +02005365 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5366 goto error;
5367
5368 {
5369 const char * const strs[2] = { "DATA\r\n" };
5370 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005371 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005372 }
5373
5374 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5375 goto error;
5376
5377 {
5378 struct tm tm;
5379 char datestr[48];
5380 const char * const strs[18] = {
5381 "From: ", p->email_alert.from, "\r\n",
5382 "To: ", p->email_alert.to, "\r\n",
5383 "Date: ", datestr, "\r\n",
5384 "Subject: [HAproxy Alert] ", msg, "\r\n",
5385 "\r\n",
5386 msg, "\r\n",
5387 "\r\n",
5388 ".\r\n",
5389 NULL
5390 };
5391
5392 get_localtime(date.tv_sec, &tm);
5393
5394 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005395 goto error;
5396 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005397
5398 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005399 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005400 }
5401
5402 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005403 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005404
5405 {
5406 const char * const strs[2] = { "QUIT\r\n" };
5407 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5408 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005409 }
5410
Christopher Faulet61cc8522020-04-20 14:54:42 +02005411 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5412 goto error;
5413
5414 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5415 task_wakeup(check->task, TASK_WOKEN_MSG);
5416 LIST_ADDQ(&q->email_alerts, &alert->list);
5417 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5418 return 1;
5419
5420error:
5421 email_alert_free(alert);
5422 return 0;
5423}
5424
5425static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5426{
5427 int i;
5428 struct mailer *mailer;
5429
5430 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5431 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5432 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5433 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5434 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005435 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005436 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005437
Christopher Faulet61cc8522020-04-20 14:54:42 +02005438 return;
5439}
5440
5441/*
5442 * Send email alert if configured.
5443 */
5444void send_email_alert(struct server *s, int level, const char *format, ...)
5445{
5446 va_list argp;
5447 char buf[1024];
5448 int len;
5449 struct proxy *p = s->proxy;
5450
5451 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5452 return;
5453
5454 va_start(argp, format);
5455 len = vsnprintf(buf, sizeof(buf), format, argp);
5456 va_end(argp);
5457
5458 if (len < 0 || len >= sizeof(buf)) {
5459 ha_alert("Email alert [%s] could not format message\n", p->id);
5460 return;
5461 }
5462
5463 enqueue_email_alert(p, s, buf);
5464}
5465
5466/**************************************************************************/
5467/************************** Check sample fetches **************************/
5468/**************************************************************************/
5469/* extracts check payload at a fixed position and length */
5470static int
5471smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5472{
5473 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5474 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5475 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5476 struct buffer *buf;
5477
5478 if (!check)
5479 return 0;
5480
5481 buf = &check->bi;
5482 if (buf_offset > b_data(buf))
5483 goto no_match;
5484 if (buf_offset + buf_size > b_data(buf))
5485 buf_size = 0;
5486
5487 /* init chunk as read only */
5488 smp->data.type = SMP_T_STR;
5489 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5490 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5491
5492 return 1;
5493
5494 no_match:
5495 smp->flags = 0;
5496 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005497}
5498
Christopher Faulet61cc8522020-04-20 14:54:42 +02005499static struct sample_fetch_kw_list smp_kws = {ILH, {
5500 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5501 { /* END */ },
5502}};
5503
5504INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5505
5506
5507/**************************************************************************/
5508/************************ Check's parsing functions ***********************/
5509/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005510/* Parses the "tcp-check" proxy keyword */
5511static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5512 struct proxy *defpx, const char *file, int line,
5513 char **errmsg)
5514{
Christopher Faulet404f9192020-04-09 23:13:54 +02005515 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005516 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005517 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005518
5519 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5520 ret = 1;
5521
Christopher Faulet404f9192020-04-09 23:13:54 +02005522 /* Deduce the ruleset name from the proxy info */
5523 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5524 ((curpx == defpx) ? "defaults" : curpx->id),
5525 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005526
Christopher Faulet61cc8522020-04-20 14:54:42 +02005527 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005528 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005529 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005530 if (rs == NULL) {
5531 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005532 goto error;
5533 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005534 }
5535
Gaetan Rivet5301b012020-02-25 17:19:17 +01005536 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005537 if (!LIST_ISEMPTY(&rs->rules)) {
5538 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005539 index = chk->index + 1;
5540 }
5541
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005542 cur_arg = 1;
5543 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005544 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005545 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005546 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005547 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005548 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005549 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005550 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005551 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005552 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5553
5554 if (!kw) {
5555 action_kw_tcp_check_build_list(&trash);
5556 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5557 "%s%s. but got '%s'",
5558 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5559 goto error;
5560 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005561 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005562 }
5563
5564 if (!chk) {
5565 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5566 goto error;
5567 }
Christopher Faulet528f4812020-04-28 10:47:28 +02005568 ret = (ret || (*errmsg != NULL)); /* Handle warning */
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005569
5570 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005571 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005572 LIST_ADDQ(&rs->rules, &chk->list);
5573
5574 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005575 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005576 /* Use this ruleset if the proxy already has tcp-check enabled */
5577 curpx->tcpcheck_rules.list = &rs->rules;
5578 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5579 }
5580 else {
5581 /* mark this ruleset as unused for now */
5582 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5583 }
5584
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005585 return ret;
5586
5587 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005588 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005589 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005590 return -1;
5591}
5592
Christopher Faulet51b129f2020-04-09 15:54:18 +02005593/* Parses the "http-check" proxy keyword */
5594static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5595 struct proxy *defpx, const char *file, int line,
5596 char **errmsg)
5597{
Christopher Faulete5870d82020-04-15 11:32:03 +02005598 struct tcpcheck_ruleset *rs = NULL;
5599 struct tcpcheck_rule *chk = NULL;
5600 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005601
5602 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5603 ret = 1;
5604
5605 cur_arg = 1;
5606 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5607 /* enable a graceful server shutdown on an HTTP 404 response */
5608 curpx->options |= PR_O_DISABLE404;
5609 if (too_many_args(1, args, errmsg, NULL))
5610 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005611 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005612 }
5613 else if (strcmp(args[cur_arg], "send-state") == 0) {
5614 /* enable emission of the apparent state of a server in HTTP checks */
5615 curpx->options2 |= PR_O2_CHK_SNDST;
5616 if (too_many_args(1, args, errmsg, NULL))
5617 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005618 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005619 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005620
Christopher Faulete5870d82020-04-15 11:32:03 +02005621 /* Deduce the ruleset name from the proxy info */
5622 chunk_printf(&trash, "*http-check-%s_%s-%d",
5623 ((curpx == defpx) ? "defaults" : curpx->id),
5624 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005625
Christopher Faulet61cc8522020-04-20 14:54:42 +02005626 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005627 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005628 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005629 if (rs == NULL) {
5630 memprintf(errmsg, "out of memory.\n");
5631 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005632 }
5633 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005634
Christopher Faulete5870d82020-04-15 11:32:03 +02005635 index = 0;
5636 if (!LIST_ISEMPTY(&rs->rules)) {
5637 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5638 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5639 index = chk->index + 1;
5640 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005641
Christopher Faulete5870d82020-04-15 11:32:03 +02005642 if (strcmp(args[cur_arg], "connect") == 0)
5643 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5644 else if (strcmp(args[cur_arg], "send") == 0)
5645 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5646 else if (strcmp(args[cur_arg], "expect") == 0)
5647 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5648 file, line, errmsg);
5649 else if (strcmp(args[cur_arg], "comment") == 0)
5650 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5651 else {
5652 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005653
Christopher Faulete5870d82020-04-15 11:32:03 +02005654 if (!kw) {
5655 action_kw_tcp_check_build_list(&trash);
5656 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5657 " 'send', 'expect'%s%s. but got '%s'",
5658 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5659 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005660 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005661 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5662 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005663
Christopher Faulete5870d82020-04-15 11:32:03 +02005664 if (!chk) {
5665 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5666 goto error;
5667 }
5668 ret = (*errmsg != NULL); /* Handle warning */
5669
5670 chk->index = index;
5671 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5672 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5673 /* Use this ruleset if the proxy already has http-check enabled */
5674 curpx->tcpcheck_rules.list = &rs->rules;
5675 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5676 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5677 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5678 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005679 goto error;
5680 }
5681 }
5682 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005683 /* mark this ruleset as unused for now */
5684 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5685 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005686 }
5687
Christopher Faulete5870d82020-04-15 11:32:03 +02005688 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005689 return ret;
5690
5691 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005692 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005693 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005694 return -1;
5695}
5696
Christopher Faulete9111b62020-04-09 18:12:08 +02005697/* Parses the "external-check" proxy keyword */
5698static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5699 struct proxy *defpx, const char *file, int line,
5700 char **errmsg)
5701{
5702 int cur_arg, ret = 0;
5703
5704 cur_arg = 1;
5705 if (!*(args[cur_arg])) {
5706 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5707 goto error;
5708 }
5709
5710 if (strcmp(args[cur_arg], "command") == 0) {
5711 if (too_many_args(2, args, errmsg, NULL))
5712 goto error;
5713 if (!*(args[cur_arg+1])) {
5714 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5715 goto error;
5716 }
5717 free(curpx->check_command);
5718 curpx->check_command = strdup(args[cur_arg+1]);
5719 }
5720 else if (strcmp(args[cur_arg], "path") == 0) {
5721 if (too_many_args(2, args, errmsg, NULL))
5722 goto error;
5723 if (!*(args[cur_arg+1])) {
5724 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5725 goto error;
5726 }
5727 free(curpx->check_path);
5728 curpx->check_path = strdup(args[cur_arg+1]);
5729 }
5730 else {
5731 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5732 args[0], args[1]);
5733 goto error;
5734 }
5735
5736 ret = (*errmsg != NULL); /* Handle warning */
5737 return ret;
5738
5739error:
5740 return -1;
5741}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005742
Christopher Faulet430e4802020-04-09 15:28:16 +02005743/* Parses the "option tcp-check" proxy keyword */
5744int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5745 const char *file, int line)
5746{
Christopher Faulet404f9192020-04-09 23:13:54 +02005747 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005748 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5749 int err_code = 0;
5750
5751 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5752 err_code |= ERR_WARN;
5753
5754 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5755 goto out;
5756
Christopher Faulet404f9192020-04-09 23:13:54 +02005757 curpx->options2 &= ~PR_O2_CHK_ANY;
5758 curpx->options2 |= PR_O2_TCPCHK_CHK;
5759
Christopher Fauletd7e63962020-04-17 20:15:59 +02005760 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005761 /* If a tcp-check rulesset is already set, do nothing */
5762 if (rules->list)
5763 goto out;
5764
5765 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5766 * get it.
5767 */
5768 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5769 goto curpx_ruleset;
5770
5771 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5772 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005773 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005774 if (rs)
5775 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005776 }
5777
Christopher Faulet404f9192020-04-09 23:13:54 +02005778 curpx_ruleset:
5779 /* Deduce the ruleset name from the proxy info */
5780 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5781 ((curpx == defpx) ? "defaults" : curpx->id),
5782 curpx->conf.file, curpx->conf.line);
5783
Christopher Faulet61cc8522020-04-20 14:54:42 +02005784 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005785 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005786 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005787 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005788 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5789 goto error;
5790 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005791 }
5792
Christopher Faulet404f9192020-04-09 23:13:54 +02005793 ruleset_found:
5794 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005795 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005796 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005797
5798 out:
5799 return err_code;
5800
5801 error:
5802 err_code |= ERR_ALERT | ERR_FATAL;
5803 goto out;
5804}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005805
5806/* Parses the "option redis-check" proxy keyword */
5807int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5808 const char *file, int line)
5809{
5810 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5811 static char *redis_res = "+PONG\r\n";
5812
5813 struct tcpcheck_ruleset *rs = NULL;
5814 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5815 struct tcpcheck_rule *chk;
5816 char *errmsg = NULL;
5817 int err_code = 0;
5818
5819 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5820 err_code |= ERR_WARN;
5821
5822 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5823 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005824
5825 curpx->options2 &= ~PR_O2_CHK_ANY;
5826 curpx->options2 |= PR_O2_TCPCHK_CHK;
5827
5828 free_tcpcheck_vars(&rules->preset_vars);
5829 rules->list = NULL;
5830 rules->flags = 0;
5831
Christopher Faulet61cc8522020-04-20 14:54:42 +02005832 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005833 if (rs)
5834 goto ruleset_found;
5835
Christopher Faulet61cc8522020-04-20 14:54:42 +02005836 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005837 if (rs == NULL) {
5838 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5839 goto error;
5840 }
5841
5842 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5843 1, curpx, &rs->rules, file, line, &errmsg);
5844 if (!chk) {
5845 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5846 goto error;
5847 }
5848 chk->index = 0;
5849 LIST_ADDQ(&rs->rules, &chk->list);
5850
5851 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5852 "error-status", "L7STS",
5853 "on-error", "%[check.payload(),cut_crlf]",
5854 "on-success", "Redis server is ok",
5855 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005856 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005857 if (!chk) {
5858 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5859 goto error;
5860 }
5861 chk->index = 1;
5862 LIST_ADDQ(&rs->rules, &chk->list);
5863
Christopher Fauletd7cee712020-04-21 13:45:00 +02005864 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005865
5866 ruleset_found:
5867 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005868 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005869
5870 out:
5871 free(errmsg);
5872 return err_code;
5873
5874 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005875 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005876 err_code |= ERR_ALERT | ERR_FATAL;
5877 goto out;
5878}
5879
Christopher Faulet811f78c2020-04-01 11:10:27 +02005880
5881/* Parses the "option ssl-hello-chk" proxy keyword */
5882int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5883 const char *file, int line)
5884{
5885 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5886 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5887 *
5888 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5889 */
5890 static char sslv3_client_hello[] = {
5891 "16" /* ContentType : 0x16 = Hanshake */
5892 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5893 "0079" /* ContentLength : 0x79 bytes after this one */
5894 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5895 "000075" /* HandshakeLength : 0x75 bytes after this one */
5896 "0300" /* Hello Version : 0x0300 = v3 */
5897 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5898 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5899 "00" /* Session ID length : empty (no session ID) */
5900 "004E" /* Cipher Suite Length : 78 bytes after this one */
5901 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5902 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5903 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5904 "000D" "000E" "000F" "0010" /* various bit lengths, */
5905 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5906 "0015" "0016" "0017" "0018"
5907 "0019" "001A" "001B" "002F"
5908 "0030" "0031" "0032" "0033"
5909 "0034" "0035" "0036" "0037"
5910 "0038" "0039" "003A"
5911 "01" /* Compression Length : 0x01 = 1 byte for types */
5912 "00" /* Compression Type : 0x00 = NULL compression */
5913 };
5914
5915 struct tcpcheck_ruleset *rs = NULL;
5916 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5917 struct tcpcheck_rule *chk;
5918 char *errmsg = NULL;
5919 int err_code = 0;
5920
5921 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5922 err_code |= ERR_WARN;
5923
5924 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5925 goto out;
5926
Christopher Faulet811f78c2020-04-01 11:10:27 +02005927 curpx->options2 &= ~PR_O2_CHK_ANY;
5928 curpx->options2 |= PR_O2_TCPCHK_CHK;
5929
5930 free_tcpcheck_vars(&rules->preset_vars);
5931 rules->list = NULL;
5932 rules->flags = 0;
5933
Christopher Faulet61cc8522020-04-20 14:54:42 +02005934 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005935 if (rs)
5936 goto ruleset_found;
5937
Christopher Faulet61cc8522020-04-20 14:54:42 +02005938 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005939 if (rs == NULL) {
5940 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5941 goto error;
5942 }
5943
5944 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5945 1, curpx, &rs->rules, file, line, &errmsg);
5946 if (!chk) {
5947 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5948 goto error;
5949 }
5950 chk->index = 0;
5951 LIST_ADDQ(&rs->rules, &chk->list);
5952
5953 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005954 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005955 "error-status", "L6RSP", "tout-status", "L6TOUT",
5956 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005957 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005958 if (!chk) {
5959 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5960 goto error;
5961 }
5962 chk->index = 1;
5963 LIST_ADDQ(&rs->rules, &chk->list);
5964
Christopher Fauletd7cee712020-04-21 13:45:00 +02005965 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005966
5967 ruleset_found:
5968 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005969 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005970
5971 out:
5972 free(errmsg);
5973 return err_code;
5974
5975 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005976 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005977 err_code |= ERR_ALERT | ERR_FATAL;
5978 goto out;
5979}
5980
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005981/* Parses the "option smtpchk" proxy keyword */
5982int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5983 const char *file, int line)
5984{
5985 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5986
5987 struct tcpcheck_ruleset *rs = NULL;
5988 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5989 struct tcpcheck_rule *chk;
5990 struct tcpcheck_var *var = NULL;
5991 char *cmd = NULL, *errmsg = NULL;
5992 int err_code = 0;
5993
5994 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5995 err_code |= ERR_WARN;
5996
5997 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5998 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005999
6000 curpx->options2 &= ~PR_O2_CHK_ANY;
6001 curpx->options2 |= PR_O2_TCPCHK_CHK;
6002
6003 free_tcpcheck_vars(&rules->preset_vars);
6004 rules->list = NULL;
6005 rules->flags = 0;
6006
6007 cur_arg += 2;
6008 if (*args[cur_arg] && *args[cur_arg+1] &&
6009 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6010 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6011 if (cmd)
6012 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6013 }
6014 else {
6015 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6016 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6017 cmd = strdup("HELO localhost");
6018 }
6019
Christopher Fauletb61caf42020-04-21 10:57:42 +02006020 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006021 if (cmd == NULL || var == NULL) {
6022 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6023 goto error;
6024 }
6025 var->data.type = SMP_T_STR;
6026 var->data.u.str.area = cmd;
6027 var->data.u.str.data = strlen(cmd);
6028 LIST_INIT(&var->list);
6029 LIST_ADDQ(&rules->preset_vars, &var->list);
6030 cmd = NULL;
6031 var = NULL;
6032
Christopher Faulet61cc8522020-04-20 14:54:42 +02006033 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006034 if (rs)
6035 goto ruleset_found;
6036
Christopher Faulet61cc8522020-04-20 14:54:42 +02006037 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006038 if (rs == NULL) {
6039 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6040 goto error;
6041 }
6042
6043 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6044 1, curpx, &rs->rules, file, line, &errmsg);
6045 if (!chk) {
6046 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6047 goto error;
6048 }
6049 chk->index = 0;
6050 LIST_ADDQ(&rs->rules, &chk->list);
6051
6052 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6053 "min-recv", "4",
6054 "error-status", "L7RSP",
6055 "on-error", "%[check.payload(),cut_crlf]",
6056 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006057 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006058 if (!chk) {
6059 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6060 goto error;
6061 }
6062 chk->index = 1;
6063 LIST_ADDQ(&rs->rules, &chk->list);
6064
6065 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6066 "min-recv", "4",
6067 "error-status", "L7STS",
6068 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6069 "status-code", "check.payload(0,3)",
6070 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006071 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006072 if (!chk) {
6073 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6074 goto error;
6075 }
6076 chk->index = 2;
6077 LIST_ADDQ(&rs->rules, &chk->list);
6078
6079 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6080 1, curpx, &rs->rules, file, line, &errmsg);
6081 if (!chk) {
6082 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6083 goto error;
6084 }
6085 chk->index = 3;
6086 LIST_ADDQ(&rs->rules, &chk->list);
6087
6088 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6089 "min-recv", "4",
6090 "error-status", "L7STS",
6091 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6092 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6093 "status-code", "check.payload(0,3)",
6094 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006095 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006096 if (!chk) {
6097 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6098 goto error;
6099 }
6100 chk->index = 4;
6101 LIST_ADDQ(&rs->rules, &chk->list);
6102
Christopher Fauletd7cee712020-04-21 13:45:00 +02006103 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006104
6105 ruleset_found:
6106 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006107 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006108
6109 out:
6110 free(errmsg);
6111 return err_code;
6112
6113 error:
6114 free(cmd);
6115 free(var);
6116 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006117 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006118 err_code |= ERR_ALERT | ERR_FATAL;
6119 goto out;
6120}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006121
Christopher Fauletce355072020-04-02 11:44:39 +02006122/* Parses the "option pgsql-check" proxy keyword */
6123int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6124 const char *file, int line)
6125{
6126 static char pgsql_req[] = {
6127 "%[var(check.plen),htonl,hex]" /* The packet length*/
6128 "00030000" /* the version 3.0 */
6129 "7573657200" /* "user" key */
6130 "%[var(check.username),hex]00" /* the username */
6131 "00"
6132 };
6133
6134 struct tcpcheck_ruleset *rs = NULL;
6135 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6136 struct tcpcheck_rule *chk;
6137 struct tcpcheck_var *var = NULL;
6138 char *user = NULL, *errmsg = NULL;
6139 size_t packetlen = 0;
6140 int err_code = 0;
6141
6142 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6143 err_code |= ERR_WARN;
6144
6145 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6146 goto out;
6147
Christopher Fauletce355072020-04-02 11:44:39 +02006148 curpx->options2 &= ~PR_O2_CHK_ANY;
6149 curpx->options2 |= PR_O2_TCPCHK_CHK;
6150
6151 free_tcpcheck_vars(&rules->preset_vars);
6152 rules->list = NULL;
6153 rules->flags = 0;
6154
6155 cur_arg += 2;
6156 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6157 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6158 file, line, args[0], args[1]);
6159 goto error;
6160 }
6161 if (strcmp(args[cur_arg], "user") == 0) {
6162 packetlen = 15 + strlen(args[cur_arg+1]);
6163 user = strdup(args[cur_arg+1]);
6164
Christopher Fauletb61caf42020-04-21 10:57:42 +02006165 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006166 if (user == NULL || var == NULL) {
6167 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6168 goto error;
6169 }
6170 var->data.type = SMP_T_STR;
6171 var->data.u.str.area = user;
6172 var->data.u.str.data = strlen(user);
6173 LIST_INIT(&var->list);
6174 LIST_ADDQ(&rules->preset_vars, &var->list);
6175 user = NULL;
6176 var = NULL;
6177
Christopher Fauletb61caf42020-04-21 10:57:42 +02006178 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006179 if (var == NULL) {
6180 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6181 goto error;
6182 }
6183 var->data.type = SMP_T_SINT;
6184 var->data.u.sint = packetlen;
6185 LIST_INIT(&var->list);
6186 LIST_ADDQ(&rules->preset_vars, &var->list);
6187 var = NULL;
6188 }
6189 else {
6190 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6191 file, line, args[0], args[1]);
6192 goto error;
6193 }
6194
Christopher Faulet61cc8522020-04-20 14:54:42 +02006195 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006196 if (rs)
6197 goto ruleset_found;
6198
Christopher Faulet61cc8522020-04-20 14:54:42 +02006199 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006200 if (rs == NULL) {
6201 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6202 goto error;
6203 }
6204
6205 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6206 1, curpx, &rs->rules, file, line, &errmsg);
6207 if (!chk) {
6208 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6209 goto error;
6210 }
6211 chk->index = 0;
6212 LIST_ADDQ(&rs->rules, &chk->list);
6213
6214 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6215 1, curpx, &rs->rules, file, line, &errmsg);
6216 if (!chk) {
6217 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6218 goto error;
6219 }
6220 chk->index = 1;
6221 LIST_ADDQ(&rs->rules, &chk->list);
6222
6223 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6224 "min-recv", "5",
6225 "error-status", "L7RSP",
6226 "on-error", "%[check.payload(6,0)]",
6227 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006228 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006229 if (!chk) {
6230 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6231 goto error;
6232 }
6233 chk->index = 2;
6234 LIST_ADDQ(&rs->rules, &chk->list);
6235
Christopher Fauletb841c742020-04-27 18:29:49 +02006236 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
Christopher Fauletce355072020-04-02 11:44:39 +02006237 "min-recv", "9",
6238 "error-status", "L7STS",
6239 "on-success", "PostgreSQL server is ok",
6240 "on-error", "PostgreSQL unknown error",
6241 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006242 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006243 if (!chk) {
6244 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6245 goto error;
6246 }
6247 chk->index = 3;
6248 LIST_ADDQ(&rs->rules, &chk->list);
6249
Christopher Fauletd7cee712020-04-21 13:45:00 +02006250 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006251
6252 ruleset_found:
6253 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006254 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006255
6256 out:
6257 free(errmsg);
6258 return err_code;
6259
6260 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006261 free(user);
6262 free(var);
6263 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006264 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006265 err_code |= ERR_ALERT | ERR_FATAL;
6266 goto out;
6267}
6268
6269
6270/* Parses the "option mysql-check" proxy keyword */
6271int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6272 const char *file, int line)
6273{
6274 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6275 * const char mysql40_client_auth_pkt[] = {
6276 * "\x0e\x00\x00" // packet length
6277 * "\x01" // packet number
6278 * "\x00\x00" // client capabilities
6279 * "\x00\x00\x01" // max packet
6280 * "haproxy\x00" // username (null terminated string)
6281 * "\x00" // filler (always 0x00)
6282 * "\x01\x00\x00" // packet length
6283 * "\x00" // packet number
6284 * "\x01" // COM_QUIT command
6285 * };
6286 */
6287 static char mysql40_rsname[] = "*mysql40-check";
6288 static char mysql40_req[] = {
6289 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6290 "0080" /* client capabilities */
6291 "000001" /* max packet */
6292 "%[var(check.username),hex]00" /* the username */
6293 "00" /* filler (always 0x00) */
6294 "010000" /* packet length*/
6295 "00" /* sequence ID */
6296 "01" /* COM_QUIT command */
6297 };
6298
6299 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6300 * const char mysql41_client_auth_pkt[] = {
6301 * "\x0e\x00\x00\" // packet length
6302 * "\x01" // packet number
6303 * "\x00\x00\x00\x00" // client capabilities
6304 * "\x00\x00\x00\x01" // max packet
6305 * "\x21" // character set (UTF-8)
6306 * char[23] // All zeroes
6307 * "haproxy\x00" // username (null terminated string)
6308 * "\x00" // filler (always 0x00)
6309 * "\x01\x00\x00" // packet length
6310 * "\x00" // packet number
6311 * "\x01" // COM_QUIT command
6312 * };
6313 */
6314 static char mysql41_rsname[] = "*mysql41-check";
6315 static char mysql41_req[] = {
6316 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6317 "00820000" /* client capabilities */
6318 "00800001" /* max packet */
6319 "21" /* character set (UTF-8) */
6320 "000000000000000000000000" /* 23 bytes, al zeroes */
6321 "0000000000000000000000"
6322 "%[var(check.username),hex]00" /* the username */
6323 "00" /* filler (always 0x00) */
6324 "010000" /* packet length*/
6325 "00" /* sequence ID */
6326 "01" /* COM_QUIT command */
6327 };
6328
6329 struct tcpcheck_ruleset *rs = NULL;
6330 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6331 struct tcpcheck_rule *chk;
6332 struct tcpcheck_var *var = NULL;
6333 char *mysql_rsname = "*mysql-check";
6334 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6335 int index = 0, err_code = 0;
6336
6337 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6338 err_code |= ERR_WARN;
6339
6340 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6341 goto out;
6342
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006343 curpx->options2 &= ~PR_O2_CHK_ANY;
6344 curpx->options2 |= PR_O2_TCPCHK_CHK;
6345
6346 free_tcpcheck_vars(&rules->preset_vars);
6347 rules->list = NULL;
6348 rules->flags = 0;
6349
6350 cur_arg += 2;
6351 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006352 int packetlen, userlen;
6353
6354 if (strcmp(args[cur_arg], "user") != 0) {
6355 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6356 file, line, args[0], args[1], args[cur_arg]);
6357 goto error;
6358 }
6359
6360 if (*(args[cur_arg+1]) == 0) {
6361 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6362 file, line, args[0], args[1], args[cur_arg]);
6363 goto error;
6364 }
6365
6366 hdr = calloc(4, sizeof(*hdr));
6367 user = strdup(args[cur_arg+1]);
6368 userlen = strlen(args[cur_arg+1]);
6369
6370 if (hdr == NULL || user == NULL) {
6371 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6372 goto error;
6373 }
6374
6375 if (*args[cur_arg+2]) {
6376 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6377 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6378 file, line, args[cur_arg], args[cur_arg+2]);
6379 goto error;
6380 }
6381 packetlen = userlen + 7 + 27;
6382 mysql_req = mysql41_req;
6383 mysql_rsname = mysql41_rsname;
6384 }
6385 else {
6386 packetlen = userlen + 7;
6387 mysql_req = mysql40_req;
6388 mysql_rsname = mysql40_rsname;
6389 }
6390
6391 hdr[0] = (unsigned char)(packetlen & 0xff);
6392 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6393 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6394 hdr[3] = 1;
6395
Christopher Fauletb61caf42020-04-21 10:57:42 +02006396 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006397 if (var == NULL) {
6398 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6399 goto error;
6400 }
6401 var->data.type = SMP_T_STR;
6402 var->data.u.str.area = hdr;
6403 var->data.u.str.data = 4;
6404 LIST_INIT(&var->list);
6405 LIST_ADDQ(&rules->preset_vars, &var->list);
6406 hdr = NULL;
6407 var = NULL;
6408
Christopher Fauletb61caf42020-04-21 10:57:42 +02006409 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006410 if (var == NULL) {
6411 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6412 goto error;
6413 }
6414 var->data.type = SMP_T_STR;
6415 var->data.u.str.area = user;
6416 var->data.u.str.data = strlen(user);
6417 LIST_INIT(&var->list);
6418 LIST_ADDQ(&rules->preset_vars, &var->list);
6419 user = NULL;
6420 var = NULL;
6421 }
6422
Christopher Faulet61cc8522020-04-20 14:54:42 +02006423 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006424 if (rs)
6425 goto ruleset_found;
6426
Christopher Faulet61cc8522020-04-20 14:54:42 +02006427 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006428 if (rs == NULL) {
6429 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6430 goto error;
6431 }
6432
6433 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6434 1, curpx, &rs->rules, file, line, &errmsg);
6435 if (!chk) {
6436 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6437 goto error;
6438 }
6439 chk->index = index++;
6440 LIST_ADDQ(&rs->rules, &chk->list);
6441
6442 if (mysql_req) {
6443 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6444 1, curpx, &rs->rules, file, line, &errmsg);
6445 if (!chk) {
6446 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6447 goto error;
6448 }
6449 chk->index = index++;
6450 LIST_ADDQ(&rs->rules, &chk->list);
6451 }
6452
6453 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006454 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006455 if (!chk) {
6456 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6457 goto error;
6458 }
6459 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6460 chk->index = index++;
6461 LIST_ADDQ(&rs->rules, &chk->list);
6462
6463 if (mysql_req) {
6464 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006465 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006466 if (!chk) {
6467 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6468 goto error;
6469 }
6470 chk->expect.custom = tcpcheck_mysql_expect_ok;
6471 chk->index = index++;
6472 LIST_ADDQ(&rs->rules, &chk->list);
6473 }
6474
Christopher Fauletd7cee712020-04-21 13:45:00 +02006475 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006476
6477 ruleset_found:
6478 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006479 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006480
6481 out:
6482 free(errmsg);
6483 return err_code;
6484
6485 error:
6486 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006487 free(user);
6488 free(var);
6489 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006490 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006491 err_code |= ERR_ALERT | ERR_FATAL;
6492 goto out;
6493}
6494
Christopher Faulet1997eca2020-04-03 23:13:50 +02006495int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6496 const char *file, int line)
6497{
6498 static char *ldap_req = "300C020101600702010304008000";
6499
6500 struct tcpcheck_ruleset *rs = NULL;
6501 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6502 struct tcpcheck_rule *chk;
6503 char *errmsg = NULL;
6504 int err_code = 0;
6505
6506 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6507 err_code |= ERR_WARN;
6508
6509 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6510 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006511
6512 curpx->options2 &= ~PR_O2_CHK_ANY;
6513 curpx->options2 |= PR_O2_TCPCHK_CHK;
6514
6515 free_tcpcheck_vars(&rules->preset_vars);
6516 rules->list = NULL;
6517 rules->flags = 0;
6518
Christopher Faulet61cc8522020-04-20 14:54:42 +02006519 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006520 if (rs)
6521 goto ruleset_found;
6522
Christopher Faulet61cc8522020-04-20 14:54:42 +02006523 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006524 if (rs == NULL) {
6525 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6526 goto error;
6527 }
6528
6529 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6530 1, curpx, &rs->rules, file, line, &errmsg);
6531 if (!chk) {
6532 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6533 goto error;
6534 }
6535 chk->index = 0;
6536 LIST_ADDQ(&rs->rules, &chk->list);
6537
6538 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6539 "min-recv", "14",
6540 "on-error", "Not LDAPv3 protocol",
6541 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006542 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006543 if (!chk) {
6544 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6545 goto error;
6546 }
6547 chk->index = 1;
6548 LIST_ADDQ(&rs->rules, &chk->list);
6549
6550 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006551 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006552 if (!chk) {
6553 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6554 goto error;
6555 }
6556 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6557 chk->index = 2;
6558 LIST_ADDQ(&rs->rules, &chk->list);
6559
Christopher Fauletd7cee712020-04-21 13:45:00 +02006560 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006561
6562 ruleset_found:
6563 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006564 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006565
6566 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006567 free(errmsg);
6568 return err_code;
6569
6570 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006571 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006572 err_code |= ERR_ALERT | ERR_FATAL;
6573 goto out;
6574}
6575
6576int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6577 const char *file, int line)
6578{
6579 struct tcpcheck_ruleset *rs = NULL;
6580 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6581 struct tcpcheck_rule *chk;
6582 char *spop_req = NULL;
6583 char *errmsg = NULL;
6584 int spop_len = 0, err_code = 0;
6585
6586 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6587 err_code |= ERR_WARN;
6588
6589 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6590 goto out;
6591
Christopher Faulet267b01b2020-04-04 10:27:09 +02006592 curpx->options2 &= ~PR_O2_CHK_ANY;
6593 curpx->options2 |= PR_O2_TCPCHK_CHK;
6594
6595 free_tcpcheck_vars(&rules->preset_vars);
6596 rules->list = NULL;
6597 rules->flags = 0;
6598
6599
Christopher Faulet61cc8522020-04-20 14:54:42 +02006600 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006601 if (rs)
6602 goto ruleset_found;
6603
Christopher Faulet61cc8522020-04-20 14:54:42 +02006604 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006605 if (rs == NULL) {
6606 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6607 goto error;
6608 }
6609
6610 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6611 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6612 goto error;
6613 }
6614 chunk_reset(&trash);
6615 dump_binary(&trash, spop_req, spop_len);
6616 trash.area[trash.data] = '\0';
6617
6618 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6619 1, curpx, &rs->rules, file, line, &errmsg);
6620 if (!chk) {
6621 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6622 goto error;
6623 }
6624 chk->index = 0;
6625 LIST_ADDQ(&rs->rules, &chk->list);
6626
6627 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006628 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006629 if (!chk) {
6630 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6631 goto error;
6632 }
6633 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6634 chk->index = 1;
6635 LIST_ADDQ(&rs->rules, &chk->list);
6636
Christopher Fauletd7cee712020-04-21 13:45:00 +02006637 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006638
6639 ruleset_found:
6640 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006641 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006642
6643 out:
6644 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006645 free(errmsg);
6646 return err_code;
6647
6648 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006649 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006650 err_code |= ERR_ALERT | ERR_FATAL;
6651 goto out;
6652}
Christopher Fauletce355072020-04-02 11:44:39 +02006653
Christopher Faulete5870d82020-04-15 11:32:03 +02006654
6655struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6656{
6657 struct tcpcheck_rule *chk = NULL;
6658 struct tcpcheck_http_hdr *hdr = NULL;
6659 char *meth = NULL, *uri = NULL, *vsn = NULL;
6660 char *hdrs, *body;
6661
6662 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6663 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6664 if (hdrs == body)
6665 hdrs = NULL;
6666 if (hdrs) {
6667 *hdrs = '\0';
6668 hdrs +=2;
6669 }
6670 if (body) {
6671 *body = '\0';
6672 body += 4;
6673 }
6674 if (hdrs || body) {
6675 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6676 " Please, consider to use 'http-check send' directive instead.");
6677 }
6678
6679 chk = calloc(1, sizeof(*chk));
6680 if (!chk) {
6681 memprintf(errmsg, "out of memory");
6682 goto error;
6683 }
6684 chk->action = TCPCHK_ACT_SEND;
6685 chk->send.type = TCPCHK_SEND_HTTP;
6686 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6687 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6688 LIST_INIT(&chk->send.http.hdrs);
6689
6690 /* Copy the method, uri and version */
6691 if (*args[cur_arg]) {
6692 if (!*args[cur_arg+1])
6693 uri = args[cur_arg];
6694 else
6695 meth = args[cur_arg];
6696 }
6697 if (*args[cur_arg+1])
6698 uri = args[cur_arg+1];
6699 if (*args[cur_arg+2])
6700 vsn = args[cur_arg+2];
6701
6702 if (meth) {
6703 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6704 chk->send.http.meth.str.area = strdup(meth);
6705 chk->send.http.meth.str.data = strlen(meth);
6706 if (!chk->send.http.meth.str.area) {
6707 memprintf(errmsg, "out of memory");
6708 goto error;
6709 }
6710 }
6711 if (uri) {
6712 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006713 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006714 memprintf(errmsg, "out of memory");
6715 goto error;
6716 }
6717 }
6718 if (vsn) {
6719 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006720 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006721 memprintf(errmsg, "out of memory");
6722 goto error;
6723 }
6724 }
6725
6726 /* Copy the header */
6727 if (hdrs) {
6728 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6729 struct h1m h1m;
6730 int i, ret;
6731
6732 /* Build and parse the request */
6733 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6734
6735 h1m.flags = H1_MF_HDRS_ONLY;
6736 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6737 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6738 &h1m, NULL);
6739 if (ret <= 0) {
6740 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6741 goto error;
6742 }
6743
Christopher Fauletb61caf42020-04-21 10:57:42 +02006744 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006745 hdr = calloc(1, sizeof(*hdr));
6746 if (!hdr) {
6747 memprintf(errmsg, "out of memory");
6748 goto error;
6749 }
6750 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006751 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006752 if (!hdr->name.ptr) {
6753 memprintf(errmsg, "out of memory");
6754 goto error;
6755 }
6756
Christopher Fauletb61caf42020-04-21 10:57:42 +02006757 ist0(tmp_hdrs[i].v);
6758 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
Christopher Faulete5870d82020-04-15 11:32:03 +02006759 goto error;
6760 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6761 }
6762 }
6763
6764 /* Copy the body */
6765 if (body) {
6766 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006767 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006768 memprintf(errmsg, "out of memory");
6769 goto error;
6770 }
6771 }
6772
6773 return chk;
6774
6775 error:
6776 free_tcpcheck_http_hdr(hdr);
6777 free_tcpcheck(chk, 0);
6778 return NULL;
6779}
6780
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006781int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6782 const char *file, int line)
6783{
Christopher Faulete5870d82020-04-15 11:32:03 +02006784 struct tcpcheck_ruleset *rs = NULL;
6785 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6786 struct tcpcheck_rule *chk;
6787 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006788 int err_code = 0;
6789
6790 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6791 err_code |= ERR_WARN;
6792
6793 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6794 goto out;
6795
Christopher Faulete5870d82020-04-15 11:32:03 +02006796 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6797 if (!chk) {
6798 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6799 goto error;
6800 }
6801 if (errmsg) {
6802 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6803 err_code |= ERR_WARN;
6804 free(errmsg);
6805 errmsg = NULL;
6806 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006807
Christopher Faulete5870d82020-04-15 11:32:03 +02006808 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006809 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006810 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006811
Christopher Faulete5870d82020-04-15 11:32:03 +02006812 free_tcpcheck_vars(&rules->preset_vars);
6813 rules->list = NULL;
6814 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006815
Christopher Faulete5870d82020-04-15 11:32:03 +02006816 /* Deduce the ruleset name from the proxy info */
6817 chunk_printf(&trash, "*http-check-%s_%s-%d",
6818 ((curpx == defpx) ? "defaults" : curpx->id),
6819 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006820
Christopher Faulet61cc8522020-04-20 14:54:42 +02006821 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006822 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006823 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006824 if (rs == NULL) {
6825 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6826 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006827 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006828 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006829
Christopher Faulete5870d82020-04-15 11:32:03 +02006830 rules->list = &rs->rules;
6831 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6832 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6833 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6834 rules->list = NULL;
6835 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006836 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006837
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006838 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006839 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006840 return err_code;
6841
6842 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006843 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006844 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006845 err_code |= ERR_ALERT | ERR_FATAL;
6846 goto out;
6847}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006848
Christopher Faulet6f557912020-04-09 15:58:50 +02006849int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6850 const char *file, int line)
6851{
6852 int err_code = 0;
6853
Christopher Faulet6f557912020-04-09 15:58:50 +02006854 curpx->options2 &= ~PR_O2_CHK_ANY;
6855 curpx->options2 |= PR_O2_EXT_CHK;
6856 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6857 goto out;
6858
6859 out:
6860 return err_code;
6861}
6862
Christopher Fauletce8111e2020-04-06 15:04:11 +02006863/* Parse the "addr" server keyword */
6864static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6865 char **errmsg)
6866{
6867 struct sockaddr_storage *sk;
6868 struct protocol *proto;
6869 int port1, port2, err_code = 0;
6870
6871
6872 if (!*args[*cur_arg+1]) {
6873 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6874 goto error;
6875 }
6876
6877 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6878 if (!sk) {
6879 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6880 goto error;
6881 }
6882
6883 proto = protocol_by_family(sk->ss_family);
6884 if (!proto || !proto->connect) {
6885 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6886 args[*cur_arg], args[*cur_arg+1]);
6887 goto error;
6888 }
6889
6890 if (port1 != port2) {
6891 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6892 args[*cur_arg], args[*cur_arg+1]);
6893 goto error;
6894 }
6895
6896 srv->check.addr = srv->agent.addr = *sk;
6897 srv->flags |= SRV_F_CHECKADDR;
6898 srv->flags |= SRV_F_AGENTADDR;
6899
6900 out:
6901 return err_code;
6902
6903 error:
6904 err_code |= ERR_ALERT | ERR_FATAL;
6905 goto out;
6906}
6907
6908
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006909/* Parse the "agent-addr" server keyword */
6910static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6911 char **errmsg)
6912{
6913 int err_code = 0;
6914
6915 if (!*(args[*cur_arg+1])) {
6916 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6917 goto error;
6918 }
6919 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6920 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6921 goto error;
6922 }
6923
6924 out:
6925 return err_code;
6926
6927 error:
6928 err_code |= ERR_ALERT | ERR_FATAL;
6929 goto out;
6930}
6931
6932/* Parse the "agent-check" server keyword */
6933static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6934 char **errmsg)
6935{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006936 struct tcpcheck_ruleset *rs = NULL;
6937 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6938 struct tcpcheck_rule *chk;
6939 int err_code = 0;
6940
6941 if (srv->do_agent)
6942 goto out;
6943
6944 if (!rules) {
6945 rules = calloc(1, sizeof(*rules));
6946 if (!rules) {
6947 memprintf(errmsg, "out of memory.");
6948 goto error;
6949 }
6950 LIST_INIT(&rules->preset_vars);
6951 srv->agent.tcpcheck_rules = rules;
6952 }
6953 rules->list = NULL;
6954 rules->flags = 0;
6955
Christopher Faulet61cc8522020-04-20 14:54:42 +02006956 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006957 if (rs)
6958 goto ruleset_found;
6959
Christopher Faulet61cc8522020-04-20 14:54:42 +02006960 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006961 if (rs == NULL) {
6962 memprintf(errmsg, "out of memory.");
6963 goto error;
6964 }
6965
6966 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6967 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6968 if (!chk) {
6969 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6970 goto error;
6971 }
6972 chk->index = 0;
6973 LIST_ADDQ(&rs->rules, &chk->list);
6974
6975 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006976 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6977 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006978 if (!chk) {
6979 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6980 goto error;
6981 }
6982 chk->expect.custom = tcpcheck_agent_expect_reply;
6983 chk->index = 1;
6984 LIST_ADDQ(&rs->rules, &chk->list);
6985
Christopher Fauletd7cee712020-04-21 13:45:00 +02006986 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006987
6988 ruleset_found:
6989 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006990 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006991 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006992
6993 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006994 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006995
6996 error:
6997 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006998 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006999 err_code |= ERR_ALERT | ERR_FATAL;
7000 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007001}
7002
7003/* Parse the "agent-inter" server keyword */
7004static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7005 char **errmsg)
7006{
7007 const char *err = NULL;
7008 unsigned int delay;
7009 int err_code = 0;
7010
7011 if (!*(args[*cur_arg+1])) {
7012 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7013 goto error;
7014 }
7015
7016 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7017 if (err == PARSE_TIME_OVER) {
7018 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7019 args[*cur_arg+1], args[*cur_arg], srv->id);
7020 goto error;
7021 }
7022 else if (err == PARSE_TIME_UNDER) {
7023 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7024 args[*cur_arg+1], args[*cur_arg], srv->id);
7025 goto error;
7026 }
7027 else if (err) {
7028 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7029 *err, srv->id);
7030 goto error;
7031 }
7032 if (delay <= 0) {
7033 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7034 delay, args[*cur_arg], srv->id);
7035 goto error;
7036 }
7037 srv->agent.inter = delay;
7038
7039 out:
7040 return err_code;
7041
7042 error:
7043 err_code |= ERR_ALERT | ERR_FATAL;
7044 goto out;
7045}
7046
7047/* Parse the "agent-port" server keyword */
7048static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7049 char **errmsg)
7050{
7051 int err_code = 0;
7052
7053 if (!*(args[*cur_arg+1])) {
7054 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7055 goto error;
7056 }
7057
7058 global.maxsock++;
7059 srv->agent.port = atol(args[*cur_arg+1]);
7060
7061 out:
7062 return err_code;
7063
7064 error:
7065 err_code |= ERR_ALERT | ERR_FATAL;
7066 goto out;
7067}
7068
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007069int set_srv_agent_send(struct server *srv, const char *send)
7070{
7071 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7072 struct tcpcheck_var *var = NULL;
7073 char *str;
7074
7075 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007076 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007077 if (str == NULL || var == NULL)
7078 goto error;
7079
7080 free_tcpcheck_vars(&rules->preset_vars);
7081
7082 var->data.type = SMP_T_STR;
7083 var->data.u.str.area = str;
7084 var->data.u.str.data = strlen(str);
7085 LIST_INIT(&var->list);
7086 LIST_ADDQ(&rules->preset_vars, &var->list);
7087
7088 return 1;
7089
7090 error:
7091 free(str);
7092 free(var);
7093 return 0;
7094}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007095
7096/* Parse the "agent-send" server keyword */
7097static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7098 char **errmsg)
7099{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007100 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007101 int err_code = 0;
7102
7103 if (!*(args[*cur_arg+1])) {
7104 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7105 goto error;
7106 }
7107
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007108 if (!rules) {
7109 rules = calloc(1, sizeof(*rules));
7110 if (!rules) {
7111 memprintf(errmsg, "out of memory.");
7112 goto error;
7113 }
7114 LIST_INIT(&rules->preset_vars);
7115 srv->agent.tcpcheck_rules = rules;
7116 }
7117
7118 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007119 memprintf(errmsg, "out of memory.");
7120 goto error;
7121 }
7122
7123 out:
7124 return err_code;
7125
7126 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007127 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007128 err_code |= ERR_ALERT | ERR_FATAL;
7129 goto out;
7130}
7131
7132/* Parse the "no-agent-send" server keyword */
7133static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7134 char **errmsg)
7135{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007136 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007137 return 0;
7138}
7139
Christopher Fauletce8111e2020-04-06 15:04:11 +02007140/* Parse the "check" server keyword */
7141static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7142 char **errmsg)
7143{
7144 srv->do_check = 1;
7145 return 0;
7146}
7147
7148/* Parse the "check-send-proxy" server keyword */
7149static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7150 char **errmsg)
7151{
7152 srv->check.send_proxy = 1;
7153 return 0;
7154}
7155
7156/* Parse the "check-via-socks4" server keyword */
7157static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7158 char **errmsg)
7159{
7160 srv->check.via_socks4 = 1;
7161 return 0;
7162}
7163
7164/* Parse the "no-check" server keyword */
7165static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7166 char **errmsg)
7167{
7168 deinit_srv_check(srv);
7169 return 0;
7170}
7171
7172/* Parse the "no-check-send-proxy" server keyword */
7173static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7174 char **errmsg)
7175{
7176 srv->check.send_proxy = 0;
7177 return 0;
7178}
7179
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007180/* parse the "check-proto" server keyword */
7181static int srv_parse_check_proto(char **args, int *cur_arg,
7182 struct proxy *px, struct server *newsrv, char **err)
7183{
7184 int err_code = 0;
7185
7186 if (!*args[*cur_arg + 1]) {
7187 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7188 goto error;
7189 }
7190 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7191 if (!newsrv->check.mux_proto) {
7192 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7193 goto error;
7194 }
7195
7196 out:
7197 return err_code;
7198
7199 error:
7200 err_code |= ERR_ALERT | ERR_FATAL;
7201 goto out;
7202}
7203
7204
Christopher Fauletce8111e2020-04-06 15:04:11 +02007205/* Parse the "rise" server keyword */
7206static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7207 char **errmsg)
7208{
7209 int err_code = 0;
7210
7211 if (!*args[*cur_arg + 1]) {
7212 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7213 goto error;
7214 }
7215
7216 srv->check.rise = atol(args[*cur_arg+1]);
7217 if (srv->check.rise <= 0) {
7218 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7219 goto error;
7220 }
7221
7222 if (srv->check.health)
7223 srv->check.health = srv->check.rise;
7224
7225 out:
7226 return err_code;
7227
7228 error:
7229 deinit_srv_agent_check(srv);
7230 err_code |= ERR_ALERT | ERR_FATAL;
7231 goto out;
7232 return 0;
7233}
7234
7235/* Parse the "fall" server keyword */
7236static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7237 char **errmsg)
7238{
7239 int err_code = 0;
7240
7241 if (!*args[*cur_arg + 1]) {
7242 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7243 goto error;
7244 }
7245
7246 srv->check.fall = atol(args[*cur_arg+1]);
7247 if (srv->check.fall <= 0) {
7248 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7249 goto error;
7250 }
7251
7252 out:
7253 return err_code;
7254
7255 error:
7256 deinit_srv_agent_check(srv);
7257 err_code |= ERR_ALERT | ERR_FATAL;
7258 goto out;
7259 return 0;
7260}
7261
7262/* Parse the "inter" server keyword */
7263static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7264 char **errmsg)
7265{
7266 const char *err = NULL;
7267 unsigned int delay;
7268 int err_code = 0;
7269
7270 if (!*(args[*cur_arg+1])) {
7271 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7272 goto error;
7273 }
7274
7275 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7276 if (err == PARSE_TIME_OVER) {
7277 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7278 args[*cur_arg+1], args[*cur_arg], srv->id);
7279 goto error;
7280 }
7281 else if (err == PARSE_TIME_UNDER) {
7282 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7283 args[*cur_arg+1], args[*cur_arg], srv->id);
7284 goto error;
7285 }
7286 else if (err) {
7287 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7288 *err, srv->id);
7289 goto error;
7290 }
7291 if (delay <= 0) {
7292 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7293 delay, args[*cur_arg], srv->id);
7294 goto error;
7295 }
7296 srv->check.inter = delay;
7297
7298 out:
7299 return err_code;
7300
7301 error:
7302 err_code |= ERR_ALERT | ERR_FATAL;
7303 goto out;
7304}
7305
7306
7307/* Parse the "fastinter" server keyword */
7308static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7309 char **errmsg)
7310{
7311 const char *err = NULL;
7312 unsigned int delay;
7313 int err_code = 0;
7314
7315 if (!*(args[*cur_arg+1])) {
7316 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7317 goto error;
7318 }
7319
7320 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7321 if (err == PARSE_TIME_OVER) {
7322 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7323 args[*cur_arg+1], args[*cur_arg], srv->id);
7324 goto error;
7325 }
7326 else if (err == PARSE_TIME_UNDER) {
7327 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7328 args[*cur_arg+1], args[*cur_arg], srv->id);
7329 goto error;
7330 }
7331 else if (err) {
7332 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7333 *err, srv->id);
7334 goto error;
7335 }
7336 if (delay <= 0) {
7337 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7338 delay, args[*cur_arg], srv->id);
7339 goto error;
7340 }
7341 srv->check.fastinter = delay;
7342
7343 out:
7344 return err_code;
7345
7346 error:
7347 err_code |= ERR_ALERT | ERR_FATAL;
7348 goto out;
7349}
7350
7351
7352/* Parse the "downinter" server keyword */
7353static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7354 char **errmsg)
7355{
7356 const char *err = NULL;
7357 unsigned int delay;
7358 int err_code = 0;
7359
7360 if (!*(args[*cur_arg+1])) {
7361 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7362 goto error;
7363 }
7364
7365 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7366 if (err == PARSE_TIME_OVER) {
7367 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7368 args[*cur_arg+1], args[*cur_arg], srv->id);
7369 goto error;
7370 }
7371 else if (err == PARSE_TIME_UNDER) {
7372 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7373 args[*cur_arg+1], args[*cur_arg], srv->id);
7374 goto error;
7375 }
7376 else if (err) {
7377 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7378 *err, srv->id);
7379 goto error;
7380 }
7381 if (delay <= 0) {
7382 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7383 delay, args[*cur_arg], srv->id);
7384 goto error;
7385 }
7386 srv->check.downinter = delay;
7387
7388 out:
7389 return err_code;
7390
7391 error:
7392 err_code |= ERR_ALERT | ERR_FATAL;
7393 goto out;
7394}
7395
7396/* Parse the "port" server keyword */
7397static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7398 char **errmsg)
7399{
7400 int err_code = 0;
7401
7402 if (!*(args[*cur_arg+1])) {
7403 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7404 goto error;
7405 }
7406
7407 global.maxsock++;
7408 srv->check.port = atol(args[*cur_arg+1]);
7409 srv->flags |= SRV_F_CHECKPORT;
7410
7411 out:
7412 return err_code;
7413
7414 error:
7415 err_code |= ERR_ALERT | ERR_FATAL;
7416 goto out;
7417}
7418
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007419static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007420 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7421 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7422 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007423 { 0, NULL, NULL },
7424}};
7425
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007426static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007427 { "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 +02007428 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7429 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7430 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7431 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7432 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007433 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007434 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007435 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7436 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007437 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007438 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7439 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7440 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7441 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7442 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7443 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7444 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7445 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007446 { NULL, NULL, 0 },
7447}};
7448
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007449INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007450INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007451
Willy Tarreaubd741542010-03-16 18:46:54 +01007452/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007453 * Local variables:
7454 * c-indent-level: 8
7455 * c-basic-offset: 8
7456 * End:
7457 */