blob: a5ddc13ba752abc5506036698fe3042853f79031 [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;
Christopher Faulet39708192020-05-05 10:47:36 +0200609 case TCPCHK_EXPECT_HTTP_HEADER:
610 chunk_appendf(chk, " (expect HTTP header pattern)");
611 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200612 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200613 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200614 break;
615 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
616 chunk_appendf(chk, " (expect HTTP body regex)");
617 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200618 case TCPCHK_EXPECT_CUSTOM:
619 chunk_appendf(chk, " (expect custom function)");
620 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100621 case TCPCHK_EXPECT_UNDEF:
622 chunk_appendf(chk, " (undefined expect!)");
623 break;
624 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200625 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200626 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200627 chunk_appendf(chk, " (send)");
628 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200629
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200630 if (check->current_step && check->current_step->comment)
631 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200632 }
633 }
634
Willy Tarreau00149122017-10-04 18:05:01 +0200635 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100636 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
638 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100639 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200640 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
641 chk->area);
642 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100643 }
644 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100645 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200646 chunk_printf(&trash, "%s%s", strerror(errno),
647 chk->area);
648 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200651 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100652 }
653 }
654
Willy Tarreau00149122017-10-04 18:05:01 +0200655 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200656 /* NOTE: this is reported after <fall> tries */
657 chunk_printf(chk, "No port available for the TCP connection");
658 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
659 }
660
Willy Tarreau00149122017-10-04 18:05:01 +0200661 if (!conn) {
662 /* connection allocation error before the connection was established */
663 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
664 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100665 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100666 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200667 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100668 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
669 else if (expired)
670 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200671
672 /*
673 * might be due to a server IP change.
674 * Let's trigger a DNS resolution if none are currently running.
675 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100676 if (check->server)
677 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200678
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100679 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100680 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100681 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200682 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100683 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
684 else if (expired)
685 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
686 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200687 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100688 /* I/O error after connection was established and before we could diagnose */
689 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
690 }
691 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200692 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
693
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100694 /* connection established but expired check */
Christopher Faulet1941bab2020-05-05 07:55:50 +0200695 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT &&
696 check->current_step->expect.tout_status != HCHK_STATUS_UNKNOWN)
Christopher Faulet811f78c2020-04-01 11:10:27 +0200697 tout = check->current_step->expect.tout_status;
698 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100699 }
700
701 return;
702}
703
Willy Tarreaubaaee002006-06-26 02:48:02 +0200704
Christopher Faulet61cc8522020-04-20 14:54:42 +0200705/**************************************************************************/
706/*************** Init/deinit tcp-check rules and ruleset ******************/
707/**************************************************************************/
708/* Releases memory allocated for a log-format string */
709static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200710{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200711 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100712
Christopher Faulet61cc8522020-04-20 14:54:42 +0200713 list_for_each_entry_safe(lf, lfb, fmt, list) {
714 LIST_DEL(&lf->list);
715 release_sample_expr(lf->expr);
716 free(lf->arg);
717 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100718 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200719}
720
Christopher Faulet61cc8522020-04-20 14:54:42 +0200721/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
722static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100723{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200724 if (!hdr)
725 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200726
Christopher Faulet61cc8522020-04-20 14:54:42 +0200727 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200728 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200729 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100730}
731
Christopher Faulet61cc8522020-04-20 14:54:42 +0200732/* Releases memory allocated for an HTTP header list used in a tcp-check send
733 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200734 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200736{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200737 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200738
Christopher Faulet61cc8522020-04-20 14:54:42 +0200739 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
740 LIST_DEL(&hdr->list);
741 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200742 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200743}
744
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
746 * tcp-check was allocated using a memory pool (it is used to instantiate email
747 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200748 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200749static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200750{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200751 if (!rule)
752 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200753
Christopher Faulet61cc8522020-04-20 14:54:42 +0200754 free(rule->comment);
755 switch (rule->action) {
756 case TCPCHK_ACT_SEND:
757 switch (rule->send.type) {
758 case TCPCHK_SEND_STRING:
759 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200760 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200761 break;
762 case TCPCHK_SEND_STRING_LF:
763 case TCPCHK_SEND_BINARY_LF:
764 free_tcpcheck_fmt(&rule->send.fmt);
765 break;
766 case TCPCHK_SEND_HTTP:
767 free(rule->send.http.meth.str.area);
768 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200769 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200770 else
771 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200772 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200773 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
774 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200775 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200776 else
777 free_tcpcheck_fmt(&rule->send.http.body_fmt);
778 break;
779 case TCPCHK_SEND_UNDEF:
780 break;
781 }
782 break;
783 case TCPCHK_ACT_EXPECT:
784 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
785 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
786 release_sample_expr(rule->expect.status_expr);
787 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200788 case TCPCHK_EXPECT_HTTP_STATUS:
789 free(rule->expect.codes.codes);
790 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 case TCPCHK_EXPECT_STRING:
792 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200793 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200794 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200795 break;
796 case TCPCHK_EXPECT_REGEX:
797 case TCPCHK_EXPECT_REGEX_BINARY:
798 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
799 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
800 regex_free(rule->expect.regex);
801 break;
Christopher Faulet39708192020-05-05 10:47:36 +0200802 case TCPCHK_EXPECT_HTTP_HEADER:
803 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
804 regex_free(rule->expect.hdr.name_re);
805 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
806 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
807 else
808 istfree(&rule->expect.hdr.name);
809
810 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
811 regex_free(rule->expect.hdr.value_re);
812 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
813 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
814 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
815 istfree(&rule->expect.hdr.value);
816 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200817 case TCPCHK_EXPECT_CUSTOM:
818 case TCPCHK_EXPECT_UNDEF:
819 break;
820 }
821 break;
822 case TCPCHK_ACT_CONNECT:
823 free(rule->connect.sni);
824 free(rule->connect.alpn);
825 release_sample_expr(rule->connect.port_expr);
826 break;
827 case TCPCHK_ACT_COMMENT:
828 break;
829 case TCPCHK_ACT_ACTION_KW:
830 free(rule->action_kw.rule);
831 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200832 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200833
834 if (in_pool)
835 pool_free(pool_head_tcpcheck_rule, rule);
836 else
837 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200838}
839
Christopher Faulet61cc8522020-04-20 14:54:42 +0200840/* Creates a tcp-check variable used in preset variables before executing a
841 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100842 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200843static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100846
Christopher Faulet61cc8522020-04-20 14:54:42 +0200847 var = calloc(1, sizeof(*var));
848 if (var == NULL)
849 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100850
Christopher Fauletb61caf42020-04-21 10:57:42 +0200851 var->name = istdup(name);
852 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200853 free(var);
854 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100855 }
Simon Horman98637e52014-06-20 12:30:16 +0900856
Christopher Faulet61cc8522020-04-20 14:54:42 +0200857 LIST_INIT(&var->list);
858 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900859}
860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861/* Releases memory allocated for a preset tcp-check variable */
862static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900863{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200864 if (!var)
865 return;
866
Christopher Fauletb61caf42020-04-21 10:57:42 +0200867 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200868 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
869 free(var->data.u.str.area);
870 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
871 free(var->data.u.meth.str.area);
872 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900873}
874
Christopher Faulet61cc8522020-04-20 14:54:42 +0200875/* Releases a list of preset tcp-check variables */
876static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900877{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200878 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200879
Christopher Faulet61cc8522020-04-20 14:54:42 +0200880 list_for_each_entry_safe(var, back, vars, list) {
881 LIST_DEL(&var->list);
882 free_tcpcheck_var(var);
883 }
Simon Horman98637e52014-06-20 12:30:16 +0900884}
885
Christopher Faulet61cc8522020-04-20 14:54:42 +0200886/* Duplicate a list of preset tcp-check variables */
887int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900888{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200889 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900890
Christopher Faulet61cc8522020-04-20 14:54:42 +0200891 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200892 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200893 if (!new)
894 goto error;
895 new->data.type = var->data.type;
896 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
897 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
898 goto error;
899 if (var->data.type == SMP_T_STR)
900 new->data.u.str.area[new->data.u.str.data] = 0;
901 }
902 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
903 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
904 goto error;
905 new->data.u.str.area[new->data.u.str.data] = 0;
906 new->data.u.meth.meth = var->data.u.meth.meth;
907 }
908 else
909 new->data.u = var->data.u;
910 LIST_ADDQ(dst, &new->list);
911 }
912 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914 error:
915 free(new);
916 return 0;
917}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919/* Looks for a shared tcp-check ruleset given its name. */
920static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
921{
922 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900924
Christopher Fauletd7cee712020-04-21 13:45:00 +0200925 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
926 if (node) {
927 rs = container_of(node, typeof(*rs), node);
928 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 }
930 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900931}
932
Christopher Faulet61cc8522020-04-20 14:54:42 +0200933/* Creates a new shared tcp-check ruleset */
934static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900935{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200936 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900937
Christopher Faulet61cc8522020-04-20 14:54:42 +0200938 rs = calloc(1, sizeof(*rs));
939 if (rs == NULL)
940 return NULL;
941
Christopher Fauletd7cee712020-04-21 13:45:00 +0200942 rs->node.key = strdup(name);
943 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200944 free(rs);
945 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947
Christopher Faulet61cc8522020-04-20 14:54:42 +0200948 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200949 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900951}
952
Christopher Faulet61cc8522020-04-20 14:54:42 +0200953/* Releases memory allocated by a tcp-check ruleset. */
954static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900955{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200956 struct tcpcheck_rule *r, *rb;
957 if (!rs)
958 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200959
Christopher Fauletd7cee712020-04-21 13:45:00 +0200960 ebpt_delete(&rs->node);
961 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200962 list_for_each_entry_safe(r, rb, &rs->rules, list) {
963 LIST_DEL(&r->list);
964 free_tcpcheck(r, 0);
965 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200966 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900967}
968
Christopher Faulet61cc8522020-04-20 14:54:42 +0200969
970/**************************************************************************/
971/**************** Everything about tcp-checks execution *******************/
972/**************************************************************************/
973/* Returns the id of a step in a tcp-check ruleset */
974static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200975{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976 if (!rule)
977 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900978
Christopher Faulet61cc8522020-04-20 14:54:42 +0200979 /* no last started step => first step */
980 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900981 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900982
Christopher Faulet61cc8522020-04-20 14:54:42 +0200983 /* last step is the first implicit connect */
984 if (rule->index == 0 &&
985 rule->action == TCPCHK_ACT_CONNECT &&
986 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
987 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900988
Christopher Faulet61cc8522020-04-20 14:54:42 +0200989 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900990}
991
Christopher Faulet61cc8522020-04-20 14:54:42 +0200992/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
993 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100994 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200995static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100996{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200997 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100998
Christopher Faulet61cc8522020-04-20 14:54:42 +0200999 list_for_each_entry(r, rules->list, list) {
1000 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1001 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001002 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001003 return NULL;
1004}
Cyril Bontéac92a062014-12-27 22:28:38 +01001005
Christopher Faulet61cc8522020-04-20 14:54:42 +02001006/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
1007 * NULL if none was found.
1008 */
1009static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
1010{
1011 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001012
Christopher Faulet61cc8522020-04-20 14:54:42 +02001013 list_for_each_entry_rev(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;
1018}
Cyril Bontéac92a062014-12-27 22:28:38 +01001019
Christopher Faulet61cc8522020-04-20 14:54:42 +02001020/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1021 * <start> or NULL if non was found. If <start> is NULL, it relies on
1022 * get_first_tcpcheck_rule().
1023 */
1024static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1025{
1026 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001027
Christopher Faulet61cc8522020-04-20 14:54:42 +02001028 if (!start)
1029 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001030
Christopher Faulet61cc8522020-04-20 14:54:42 +02001031 r = LIST_NEXT(&start->list, typeof(r), list);
1032 list_for_each_entry_from(r, rules->list, list) {
1033 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1034 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001035 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001037}
Simon Horman98637e52014-06-20 12:30:16 +09001038
Simon Horman98637e52014-06-20 12:30:16 +09001039
Christopher Faulet61cc8522020-04-20 14:54:42 +02001040/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1041static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1042 int match, struct ist info)
1043{
1044 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001045
Christopher Faulet61cc8522020-04-20 14:54:42 +02001046 /* Follows these step to produce the info message:
1047 * 1. if info field is already provided, copy it
1048 * 2. if the expect rule provides an onerror log-format string,
1049 * use it to produce the message
1050 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1051 * 4. Otherwise produce the generic tcp-check info message
1052 */
1053 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001056 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001057 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1058 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1059 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001060 }
Simon Horman98637e52014-06-20 12:30:16 +09001061
Christopher Faulet61cc8522020-04-20 14:54:42 +02001062 if (check->type == PR_O2_TCPCHK_CHK &&
1063 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1064 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001065
Christopher Faulet61cc8522020-04-20 14:54:42 +02001066 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1067 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001068 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001069 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001072 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001073 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001074 tcpcheck_get_step_id(check, rule));
1075 break;
1076 case TCPCHK_EXPECT_BINARY:
1077 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1078 break;
1079 case TCPCHK_EXPECT_REGEX:
1080 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1081 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1082 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1083 break;
1084 case TCPCHK_EXPECT_REGEX_BINARY:
1085 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001086 break;
1087 case TCPCHK_EXPECT_CUSTOM:
1088 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1089 break;
Christopher Faulet39708192020-05-05 10:47:36 +02001090 case TCPCHK_EXPECT_HTTP_HEADER:
1091 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001092 case TCPCHK_EXPECT_UNDEF:
1093 /* Should never happen. */
1094 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001095 }
1096
Christopher Faulet61cc8522020-04-20 14:54:42 +02001097 comment:
1098 /* If the failing expect rule provides a comment, it is concatenated to
1099 * the info message.
1100 */
1101 if (rule->comment) {
1102 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001103 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001104 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001105
Christopher Faulet61cc8522020-04-20 14:54:42 +02001106 /* Finally, the check status code is set if the failing expect rule
1107 * defines a status expression.
1108 */
1109 if (rule->expect.status_expr) {
1110 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
Christopher Faulet78f371e2020-04-30 09:38:08 +02001111 rule->expect.status_expr, SMP_T_STR);
1112
1113 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
1114 sample_casts[smp->data.type][SMP_T_SINT](smp))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001115 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001116 }
Simon Horman98637e52014-06-20 12:30:16 +09001117
Christopher Faulet61cc8522020-04-20 14:54:42 +02001118 *(b_tail(msg)) = '\0';
1119}
Cyril Bontéac92a062014-12-27 22:28:38 +01001120
Christopher Faulet61cc8522020-04-20 14:54:42 +02001121/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1122static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1123 struct ist info)
1124{
1125 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001126
Christopher Faulet61cc8522020-04-20 14:54:42 +02001127 /* Follows these step to produce the info message:
1128 * 1. if info field is already provided, copy it
1129 * 2. if the expect rule provides an onsucces log-format string,
1130 * use it to produce the message
1131 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1132 * 4. Otherwise produce the generic tcp-check info message
1133 */
1134 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001135 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1137 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1138 &rule->expect.onsuccess_fmt);
1139 else if (check->type == PR_O2_TCPCHK_CHK &&
1140 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1141 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001142
Christopher Faulet61cc8522020-04-20 14:54:42 +02001143 /* Finally, the check status code is set if the expect rule defines a
1144 * status expression.
1145 */
1146 if (rule->expect.status_expr) {
1147 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
Christopher Faulet78f371e2020-04-30 09:38:08 +02001148 rule->expect.status_expr, SMP_T_STR);
1149
1150 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
1151 sample_casts[smp->data.type][SMP_T_SINT](smp))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001152 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001153 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001154
1155 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001156}
1157
Christopher Faulet61cc8522020-04-20 14:54:42 +02001158/* Builds the server state header used by HTTP health-checks */
1159static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001160{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001161 int sv_state;
1162 int ratio;
1163 char addr[46];
1164 char port[6];
1165 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1166 "UP %d/%d", "UP",
1167 "NOLB %d/%d", "NOLB",
1168 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001169
Christopher Faulet61cc8522020-04-20 14:54:42 +02001170 if (!(s->check.state & CHK_ST_ENABLED))
1171 sv_state = 6;
1172 else if (s->cur_state != SRV_ST_STOPPED) {
1173 if (s->check.health == s->check.rise + s->check.fall - 1)
1174 sv_state = 3; /* UP */
1175 else
1176 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001177
Christopher Faulet61cc8522020-04-20 14:54:42 +02001178 if (s->cur_state == SRV_ST_STOPPING)
1179 sv_state += 2;
1180 } else {
1181 if (s->check.health)
1182 sv_state = 1; /* going up */
1183 else
1184 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001185 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 chunk_appendf(buf, srv_hlt_st[sv_state],
1188 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1189 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001190
Christopher Faulet61cc8522020-04-20 14:54:42 +02001191 addr_to_str(&s->addr, addr, sizeof(addr));
1192 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1193 snprintf(port, sizeof(port), "%u", s->svc_port);
1194 else
1195 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001196
Christopher Faulet61cc8522020-04-20 14:54:42 +02001197 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1198 addr, port, s->proxy->id, s->id,
1199 global.node,
1200 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1201 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1202 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1203 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001204
Christopher Faulet61cc8522020-04-20 14:54:42 +02001205 if ((s->cur_state == SRV_ST_STARTING) &&
1206 now.tv_sec < s->last_change + s->slowstart &&
1207 now.tv_sec >= s->last_change) {
1208 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1209 chunk_appendf(buf, "; throttle=%d%%", ratio);
1210 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001211
Christopher Faulet61cc8522020-04-20 14:54:42 +02001212 return b_data(buf);
1213}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001214
Christopher Faulet61cc8522020-04-20 14:54:42 +02001215/* Internal functions to parse and validate a MySQL packet in the context of an
1216 * expect rule. It start to parse the input buffer at the offset <offset>. If
1217 * <last_read> is set, no more data are expected.
1218 */
1219static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1220 unsigned int offset, int last_read)
1221{
1222 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1223 enum healthcheck_status status;
1224 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001225 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001226 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001227
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001228
Christopher Faulet61cc8522020-04-20 14:54:42 +02001229 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001230 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001231 if (!last_read)
1232 goto wait_more_data;
1233
1234 /* invalid length or truncated response */
1235 status = HCHK_STATUS_L7RSP;
1236 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001237 }
1238
Christopher Faulet61cc8522020-04-20 14:54:42 +02001239 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1240 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1241 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001242
Christopher Faulet61cc8522020-04-20 14:54:42 +02001243 if (b_data(&check->bi) < offset+plen+4) {
1244 if (!last_read)
1245 goto wait_more_data;
1246
1247 /* invalid length or truncated response */
1248 status = HCHK_STATUS_L7RSP;
1249 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001250 }
Simon Horman98637e52014-06-20 12:30:16 +09001251
Christopher Faulet61cc8522020-04-20 14:54:42 +02001252 if (*b_peek(&check->bi, offset+4) == '\xff') {
1253 /* MySQL Error packet always begin with field_count = 0xff */
1254 status = HCHK_STATUS_L7STS;
1255 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1256 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1257 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1258 goto error;
1259 }
Simon Horman98637e52014-06-20 12:30:16 +09001260
Christopher Faulet61cc8522020-04-20 14:54:42 +02001261 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1262 /* Not the last rule, continue */
1263 goto out;
1264 }
Simon Horman98637e52014-06-20 12:30:16 +09001265
Christopher Faulet61cc8522020-04-20 14:54:42 +02001266 /* We set the MySQL Version in description for information purpose
1267 * FIXME : it can be cool to use MySQL Version for other purpose,
1268 * like mark as down old MySQL server.
1269 */
Christopher Faulet1941bab2020-05-05 07:55:50 +02001270 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1271 set_server_check_status(check, status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001272
Christopher Faulet61cc8522020-04-20 14:54:42 +02001273 out:
1274 free_trash_chunk(msg);
1275 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001276
Christopher Faulet61cc8522020-04-20 14:54:42 +02001277 error:
1278 ret = TCPCHK_EVAL_STOP;
1279 check->code = err;
1280 msg = alloc_trash_chunk();
1281 if (msg)
1282 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1283 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1284 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001285
Christopher Faulet61cc8522020-04-20 14:54:42 +02001286 wait_more_data:
1287 ret = TCPCHK_EVAL_WAIT;
1288 goto out;
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 MySQL initial
1292 * handshake 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_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1299}
Simon Horman98637e52014-06-20 12:30:16 +09001300
Christopher Faulet61cc8522020-04-20 14:54:42 +02001301/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1302 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1303 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1304 * an error occurred.
1305 */
1306static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1307{
1308 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001309
Christopher Faulet61cc8522020-04-20 14:54:42 +02001310 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1311 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1312 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001313
Christopher Faulet61cc8522020-04-20 14:54:42 +02001314 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1315}
Simon Horman98637e52014-06-20 12:30:16 +09001316
Christopher Faulet61cc8522020-04-20 14:54:42 +02001317/* Custom tcp-check expect function to parse and validate the LDAP bind response
1318 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1319 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1320 * error occurred.
1321 */
1322static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1323{
1324 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1325 enum healthcheck_status status;
1326 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001327 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001328 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001329
Christopher Faulet61cc8522020-04-20 14:54:42 +02001330 /* Check if the server speaks LDAP (ASN.1/BER)
1331 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1332 * http://tools.ietf.org/html/rfc4511
1333 */
1334 /* size of LDAPMessage */
1335 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001336
Christopher Faulet61cc8522020-04-20 14:54:42 +02001337 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1338 * messageID: 0x02 0x01 0x01: INTEGER 1
1339 * protocolOp: 0x61: bindResponse
1340 */
1341 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1342 status = HCHK_STATUS_L7RSP;
1343 desc = ist("Not LDAPv3 protocol");
1344 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001345 }
Simon Horman98637e52014-06-20 12:30:16 +09001346
Christopher Faulet61cc8522020-04-20 14:54:42 +02001347 /* size of bindResponse */
1348 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001349
Christopher Faulet61cc8522020-04-20 14:54:42 +02001350 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1351 * ldapResult: 0x0a 0x01: ENUMERATION
1352 */
1353 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1354 status = HCHK_STATUS_L7RSP;
1355 desc = ist("Not LDAPv3 protocol");
1356 goto error;
1357 }
Simon Horman98637e52014-06-20 12:30:16 +09001358
Christopher Faulet61cc8522020-04-20 14:54:42 +02001359 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1360 * resultCode
1361 */
1362 check->code = *(b_head(&check->bi) + msglen + 9);
1363 if (check->code) {
1364 status = HCHK_STATUS_L7STS;
1365 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1366 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001367 }
1368
Christopher Faulet1941bab2020-05-05 07:55:50 +02001369 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1370 set_server_check_status(check, status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001371
Christopher Faulet61cc8522020-04-20 14:54:42 +02001372 out:
1373 free_trash_chunk(msg);
1374 return ret;
1375
1376 error:
1377 ret = TCPCHK_EVAL_STOP;
1378 msg = alloc_trash_chunk();
1379 if (msg)
1380 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1381 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1382 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001383}
1384
Christopher Faulet61cc8522020-04-20 14:54:42 +02001385/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1386 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1387 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001388 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001389static 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 +02001390{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001391 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1392 enum healthcheck_status status;
1393 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001394 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001395 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001396
Willy Tarreaubaaee002006-06-26 02:48:02 +02001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 memcpy(&framesz, b_head(&check->bi), 4);
1399 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001400
Christopher Faulet61cc8522020-04-20 14:54:42 +02001401 if (!last_read && b_data(&check->bi) < (4+framesz))
1402 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001403
Christopher Faulet61cc8522020-04-20 14:54:42 +02001404 memset(b_orig(&trash), 0, b_size(&trash));
1405 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1406 status = HCHK_STATUS_L7RSP;
1407 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1408 goto error;
1409 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001410
Christopher Faulet1941bab2020-05-05 07:55:50 +02001411 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1412 set_server_check_status(check, status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001413
Christopher Faulet61cc8522020-04-20 14:54:42 +02001414 out:
1415 free_trash_chunk(msg);
1416 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001417
Christopher Faulet61cc8522020-04-20 14:54:42 +02001418 error:
1419 ret = TCPCHK_EVAL_STOP;
1420 msg = alloc_trash_chunk();
1421 if (msg)
1422 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1423 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1424 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001425
Christopher Faulet61cc8522020-04-20 14:54:42 +02001426 wait_more_data:
1427 ret = TCPCHK_EVAL_WAIT;
1428 goto out;
1429}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001430
Christopher Faulet61cc8522020-04-20 14:54:42 +02001431/* Custom tcp-check expect function to parse and validate the agent-check
1432 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1433 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1434 */
1435static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1436{
1437 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1438 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1439 const char *hs = NULL; /* health status */
1440 const char *as = NULL; /* admin status */
1441 const char *ps = NULL; /* performance status */
1442 const char *cs = NULL; /* maxconn */
1443 const char *err = NULL; /* first error to report */
1444 const char *wrn = NULL; /* first warning to report */
1445 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 /* We're getting an agent check response. The agent could
1448 * have been disabled in the mean time with a long check
1449 * still pending. It is important that we ignore the whole
1450 * response.
1451 */
1452 if (!(check->state & CHK_ST_ENABLED))
1453 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001454
Christopher Faulet61cc8522020-04-20 14:54:42 +02001455 /* The agent supports strings made of a single line ended by the
1456 * first CR ('\r') or LF ('\n'). This line is composed of words
1457 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1458 * line may optionally contained a description of a state change
1459 * after a sharp ('#'), which is only considered if a health state
1460 * is announced.
1461 *
1462 * Words may be composed of :
1463 * - a numeric weight suffixed by the percent character ('%').
1464 * - a health status among "up", "down", "stopped", and "fail".
1465 * - an admin status among "ready", "drain", "maint".
1466 *
1467 * These words may appear in any order. If multiple words of the
1468 * same category appear, the last one wins.
1469 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001470
Christopher Faulet61cc8522020-04-20 14:54:42 +02001471 p = b_head(&check->bi);
1472 while (*p && *p != '\n' && *p != '\r')
1473 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001474
Christopher Faulet61cc8522020-04-20 14:54:42 +02001475 if (!*p) {
1476 if (!last_read)
1477 goto wait_more_data;
1478
1479 /* at least inform the admin that the agent is mis-behaving */
1480 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1481 goto out;
1482 }
1483
1484 *p = 0;
1485 cmd = b_head(&check->bi);
1486
1487 while (*cmd) {
1488 /* look for next word */
1489 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1490 cmd++;
1491 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001492 }
1493
Christopher Faulet61cc8522020-04-20 14:54:42 +02001494 if (*cmd == '#') {
1495 /* this is the beginning of a health status description,
1496 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001497 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001498 cmd++;
1499 while (*cmd == '\t' || *cmd == ' ')
1500 cmd++;
1501 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001502 }
1503
Christopher Faulet61cc8522020-04-20 14:54:42 +02001504 /* find the end of the word so that we have a null-terminated
1505 * word between <cmd> and <p>.
1506 */
1507 p = cmd + 1;
1508 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1509 p++;
1510 if (*p)
1511 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001512
Christopher Faulet61cc8522020-04-20 14:54:42 +02001513 /* first, health statuses */
1514 if (strcasecmp(cmd, "up") == 0) {
1515 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1516 status = HCHK_STATUS_L7OKD;
1517 hs = cmd;
1518 }
1519 else if (strcasecmp(cmd, "down") == 0) {
1520 check->server->check.health = 0;
1521 status = HCHK_STATUS_L7STS;
1522 hs = cmd;
1523 }
1524 else if (strcasecmp(cmd, "stopped") == 0) {
1525 check->server->check.health = 0;
1526 status = HCHK_STATUS_L7STS;
1527 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001528 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001529 else if (strcasecmp(cmd, "fail") == 0) {
1530 check->server->check.health = 0;
1531 status = HCHK_STATUS_L7STS;
1532 hs = cmd;
1533 }
1534 /* admin statuses */
1535 else if (strcasecmp(cmd, "ready") == 0) {
1536 as = cmd;
1537 }
1538 else if (strcasecmp(cmd, "drain") == 0) {
1539 as = cmd;
1540 }
1541 else if (strcasecmp(cmd, "maint") == 0) {
1542 as = cmd;
1543 }
1544 /* try to parse a weight here and keep the last one */
1545 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1546 ps = cmd;
1547 }
1548 /* try to parse a maxconn here */
1549 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1550 cs = cmd;
1551 }
1552 else {
1553 /* keep a copy of the first error */
1554 if (!err)
1555 err = cmd;
1556 }
1557 /* skip to next word */
1558 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001559 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 /* here, cmd points either to \0 or to the beginning of a
1561 * description. Skip possible leading spaces.
1562 */
1563 while (*cmd == ' ' || *cmd == '\n')
1564 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001565
Christopher Faulet61cc8522020-04-20 14:54:42 +02001566 /* First, update the admin status so that we avoid sending other
1567 * possibly useless warnings and can also update the health if
1568 * present after going back up.
1569 */
1570 if (as) {
1571 if (strcasecmp(as, "drain") == 0)
1572 srv_adm_set_drain(check->server);
1573 else if (strcasecmp(as, "maint") == 0)
1574 srv_adm_set_maint(check->server);
1575 else
1576 srv_adm_set_ready(check->server);
1577 }
Simon Horman98637e52014-06-20 12:30:16 +09001578
Christopher Faulet61cc8522020-04-20 14:54:42 +02001579 /* now change weights */
1580 if (ps) {
1581 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001582
Christopher Faulet61cc8522020-04-20 14:54:42 +02001583 msg = server_parse_weight_change_request(check->server, ps);
1584 if (!wrn || !*wrn)
1585 wrn = msg;
1586 }
Simon Horman98637e52014-06-20 12:30:16 +09001587
Christopher Faulet61cc8522020-04-20 14:54:42 +02001588 if (cs) {
1589 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001590
Christopher Faulet61cc8522020-04-20 14:54:42 +02001591 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001592
Christopher Faulet61cc8522020-04-20 14:54:42 +02001593 msg = server_parse_maxconn_change_request(check->server, cs);
1594 if (!wrn || !*wrn)
1595 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001596 }
1597
Christopher Faulet61cc8522020-04-20 14:54:42 +02001598 /* and finally health status */
1599 if (hs) {
1600 /* We'll report some of the warnings and errors we have
1601 * here. Down reports are critical, we leave them untouched.
1602 * Lack of report, or report of 'UP' leaves the room for
1603 * ERR first, then WARN.
1604 */
1605 const char *msg = cmd;
1606 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001607
Christopher Faulet61cc8522020-04-20 14:54:42 +02001608 if (!*msg || status == HCHK_STATUS_L7OKD) {
1609 if (err && *err)
1610 msg = err;
1611 else if (wrn && *wrn)
1612 msg = wrn;
1613 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001614
Christopher Faulet61cc8522020-04-20 14:54:42 +02001615 t = get_trash_chunk();
1616 chunk_printf(t, "via agent : %s%s%s%s",
1617 hs, *msg ? " (" : "",
1618 msg, *msg ? ")" : "");
1619 set_server_check_status(check, status, t->area);
1620 }
1621 else if (err && *err) {
1622 /* No status change but we'd like to report something odd.
1623 * Just report the current state and copy the message.
1624 */
1625 chunk_printf(&trash, "agent reports an error : %s", err);
1626 set_server_check_status(check, status/*check->status*/, trash.area);
1627 }
1628 else if (wrn && *wrn) {
1629 /* No status change but we'd like to report something odd.
1630 * Just report the current state and copy the message.
1631 */
1632 chunk_printf(&trash, "agent warns : %s", wrn);
1633 set_server_check_status(check, status/*check->status*/, trash.area);
1634 }
1635 else
1636 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001637
Christopher Faulet61cc8522020-04-20 14:54:42 +02001638 out:
1639 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001640
Christopher Faulet61cc8522020-04-20 14:54:42 +02001641 wait_more_data:
1642 ret = TCPCHK_EVAL_WAIT;
1643 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001644}
1645
Christopher Faulet61cc8522020-04-20 14:54:42 +02001646/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1647 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1648 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001649 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001650static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001651{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001652 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1653 struct tcpcheck_connect *connect = &rule->connect;
1654 struct proxy *proxy = check->proxy;
1655 struct server *s = check->server;
1656 struct task *t = check->task;
1657 struct conn_stream *cs;
1658 struct connection *conn = NULL;
1659 struct protocol *proto;
1660 struct xprt_ops *xprt;
1661 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001662
Christopher Faulet61cc8522020-04-20 14:54:42 +02001663 /* For a connect action we'll create a new connection. We may also have
1664 * to kill a previous one. But we don't want to leave *without* a
1665 * connection if we came here from the connection layer, hence with a
1666 * connection. Thus we'll proceed in the following order :
1667 * 1: close but not release previous connection (handled by the caller)
1668 * 2: try to get a new connection
1669 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001670 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001671
Christopher Faulet61cc8522020-04-20 14:54:42 +02001672 /* 2- prepare new connection */
1673 cs = cs_new(NULL);
1674 if (!cs) {
1675 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1676 tcpcheck_get_step_id(check, rule));
1677 if (rule->comment)
1678 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1679 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1680 ret = TCPCHK_EVAL_STOP;
1681 goto out;
1682 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001683
Christopher Faulet61cc8522020-04-20 14:54:42 +02001684 /* 3- release and replace the old one on success */
1685 if (check->cs) {
1686 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001687 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1688 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001689
1690 /* We may have been scheduled to run, and the I/O handler
1691 * expects to have a cs, so remove the tasklet
1692 */
1693 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1694 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001695 }
1696
Christopher Faulet61cc8522020-04-20 14:54:42 +02001697 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001698
Christopher Faulet61cc8522020-04-20 14:54:42 +02001699 check->cs = cs;
1700 conn = cs->conn;
1701 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001702
Christopher Faulet61cc8522020-04-20 14:54:42 +02001703 /* Maybe there were an older connection we were waiting on */
1704 check->wait_list.events = 0;
1705 conn->target = s ? &s->obj_type : &proxy->obj_type;
1706
1707 /* no client address */
1708 if (!sockaddr_alloc(&conn->dst)) {
1709 status = SF_ERR_RESOURCE;
1710 goto fail_check;
1711 }
1712
1713 /* connect to the connect rule addr if specified, otherwise the check
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001714 * addr if specified on the server. otherwise, use the server addr (it
1715 * MUST exist at this step).
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001716 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001717 *conn->dst = (is_addr(&connect->addr)
1718 ? connect->addr
1719 : (is_addr(&check->addr) ? check->addr : s->addr));
1720 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 port = 0;
1723 if (!port && connect->port)
1724 port = connect->port;
1725 if (!port && connect->port_expr) {
1726 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001727
Christopher Faulet61cc8522020-04-20 14:54:42 +02001728 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1729 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1730 connect->port_expr, SMP_T_SINT);
1731 if (smp)
1732 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001733 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001734 if (!port && is_inet_addr(&connect->addr))
1735 port = get_host_port(&connect->addr);
1736 if (!port && check->port)
1737 port = check->port;
1738 if (!port && is_inet_addr(&check->addr))
1739 port = get_host_port(&check->addr);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001740 if (!port) {
1741 /* The server MUST exist here */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 port = s->svc_port;
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001743 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001744 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001745
Christopher Faulet61cc8522020-04-20 14:54:42 +02001746 xprt = ((connect->options & TCPCHK_OPT_SSL)
1747 ? xprt_get(XPRT_SSL)
1748 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001749
Christopher Faulet61cc8522020-04-20 14:54:42 +02001750 conn_prepare(conn, proto, xprt);
1751 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001752
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 status = SF_ERR_INTERNAL;
1754 if (proto && proto->connect) {
1755 struct tcpcheck_rule *next;
1756 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001757
Christopher Faulet61cc8522020-04-20 14:54:42 +02001758 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1759 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001760
Christopher Faulet61cc8522020-04-20 14:54:42 +02001761 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1762 if (!next || next->action != TCPCHK_ACT_EXPECT)
1763 flags |= CONNECT_DELACK_ALWAYS;
1764 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001765 }
1766
Christopher Faulet61cc8522020-04-20 14:54:42 +02001767 if (status != SF_ERR_NONE)
1768 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001769
Christopher Faulet61cc8522020-04-20 14:54:42 +02001770 conn->flags |= CO_FL_PRIVATE;
1771 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001772
Christopher Faulet61cc8522020-04-20 14:54:42 +02001773 /* The mux may be initialized now if there isn't server attached to the
1774 * check (email alerts) or if there is a mux proto specified or if there
1775 * is no alpn.
1776 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001777 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1778 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001779 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001780
Christopher Faulet61cc8522020-04-20 14:54:42 +02001781 if (connect->mux_proto)
1782 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001783 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001784 mux_ops = check->mux_proto->mux;
1785 else {
1786 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1787 ? PROTO_MODE_HTTP
1788 : PROTO_MODE_TCP);
1789
1790 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001791 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001792 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1793 status = SF_ERR_INTERNAL;
1794 goto fail_check;
1795 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001796 }
1797
Christopher Faulet61cc8522020-04-20 14:54:42 +02001798#ifdef USE_OPENSSL
1799 if (connect->sni)
1800 ssl_sock_set_servername(conn, connect->sni);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001801 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001802 ssl_sock_set_servername(conn, s->check.sni);
1803
1804 if (connect->alpn)
1805 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001806 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001807 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1808#endif
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001809 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001810 conn->send_proxy_ofs = 1;
1811 conn->flags |= CO_FL_SOCKS4;
1812 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001813 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 +02001814 conn->send_proxy_ofs = 1;
1815 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001816 }
1817
Christopher Faulet61cc8522020-04-20 14:54:42 +02001818 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1819 conn->send_proxy_ofs = 1;
1820 conn->flags |= CO_FL_SEND_PROXY;
1821 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001822 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 +02001823 conn->send_proxy_ofs = 1;
1824 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001825 }
1826
Christopher Faulet61cc8522020-04-20 14:54:42 +02001827 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1828 /* Some servers don't like reset on close */
1829 fdtab[cs->conn->handle.fd].linger_risk = 0;
1830 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001831
Christopher Faulet61cc8522020-04-20 14:54:42 +02001832 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1833 if (xprt_add_hs(conn) < 0)
1834 status = SF_ERR_RESOURCE;
1835 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001836
Christopher Faulet61cc8522020-04-20 14:54:42 +02001837 fail_check:
1838 /* It can return one of :
1839 * - SF_ERR_NONE if everything's OK
1840 * - SF_ERR_SRVTO if there are no more servers
1841 * - SF_ERR_SRVCL if the connection was refused by the server
1842 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1843 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1844 * - SF_ERR_INTERNAL for any other purely internal errors
1845 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1846 * Note that we try to prevent the network stack from sending the ACK during the
1847 * connect() when a pure TCP check is used (without PROXY protocol).
1848 */
1849 switch (status) {
1850 case SF_ERR_NONE:
1851 /* we allow up to min(inter, timeout.connect) for a connection
1852 * to establish but only when timeout.check is set as it may be
1853 * to short for a full check otherwise
1854 */
1855 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001856
Christopher Faulet61cc8522020-04-20 14:54:42 +02001857 if (proxy->timeout.check && proxy->timeout.connect) {
1858 int t_con = tick_add(now_ms, proxy->timeout.connect);
1859 t->expire = tick_first(t->expire, t_con);
1860 }
1861 break;
1862 case SF_ERR_SRVTO: /* ETIMEDOUT */
1863 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1864 case SF_ERR_PRXCOND:
1865 case SF_ERR_RESOURCE:
1866 case SF_ERR_INTERNAL:
1867 chk_report_conn_err(check, errno, 0);
1868 ret = TCPCHK_EVAL_STOP;
1869 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001870 }
1871
Christopher Faulet61cc8522020-04-20 14:54:42 +02001872 /* don't do anything until the connection is established */
1873 if (conn->flags & CO_FL_WAIT_XPRT) {
1874 ret = TCPCHK_EVAL_WAIT;
1875 goto out;
1876 }
1877
1878 out:
1879 if (conn && check->result == CHK_RES_FAILED)
1880 conn->flags |= CO_FL_ERROR;
1881 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001882}
1883
Christopher Faulet61cc8522020-04-20 14:54:42 +02001884/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1885 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1886 * TCPCHK_EVAL_STOP if an error occurred.
1887 */
1888static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001889{
1890 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001891 struct tcpcheck_send *send = &rule->send;
1892 struct conn_stream *cs = check->cs;
1893 struct connection *conn = cs_conn(cs);
1894 struct buffer *tmp = NULL;
1895 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001896
Christopher Faulet61cc8522020-04-20 14:54:42 +02001897 /* reset the read & write buffer */
1898 b_reset(&check->bi);
1899 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001900
Christopher Faulet61cc8522020-04-20 14:54:42 +02001901 switch (send->type) {
1902 case TCPCHK_SEND_STRING:
1903 case TCPCHK_SEND_BINARY:
1904 if (istlen(send->data) >= b_size(&check->bo)) {
1905 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1906 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1907 tcpcheck_get_step_id(check, rule));
1908 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1909 ret = TCPCHK_EVAL_STOP;
1910 goto out;
1911 }
1912 b_putist(&check->bo, send->data);
1913 break;
1914 case TCPCHK_SEND_STRING_LF:
1915 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1916 if (!b_data(&check->bo))
1917 goto out;
1918 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001919 case TCPCHK_SEND_BINARY_LF: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02001920 int len = b_size(&check->bo);
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001921
Christopher Faulet61cc8522020-04-20 14:54:42 +02001922 tmp = alloc_trash_chunk();
1923 if (!tmp)
1924 goto error_lf;
1925 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1926 if (!b_data(tmp))
1927 goto out;
1928 tmp->area[tmp->data] = '\0';
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001929 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001930 goto error_lf;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001931 check->bo.data = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001932 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001933 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001934 case TCPCHK_SEND_HTTP: {
1935 struct htx_sl *sl;
1936 struct ist meth, uri, vsn, clen, body;
1937 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001938
Christopher Faulet61cc8522020-04-20 14:54:42 +02001939 tmp = alloc_trash_chunk();
1940 if (!tmp)
1941 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001942
Christopher Faulet61cc8522020-04-20 14:54:42 +02001943 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1944 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1945 : http_known_methods[send->http.meth.meth]);
1946 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1947 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001948
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001949 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1950 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001951 slflags |= HTX_SL_F_VER_11;
1952 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1953 if (!isttest(send->http.body))
1954 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001955
Christopher Faulet61cc8522020-04-20 14:54:42 +02001956 htx = htx_from_buf(&check->bo);
1957 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1958 if (!sl)
1959 goto error_htx;
1960 sl->info.req.meth = send->http.meth.meth;
Christopher Faulet39959a72020-04-29 13:21:37 +02001961 if (!http_update_host(htx, sl, uri))
1962 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001963
Christopher Faulet61cc8522020-04-20 14:54:42 +02001964 body = send->http.body; // TODO: handle body_fmt
1965 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001966
Christopher Faulet61cc8522020-04-20 14:54:42 +02001967 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1968 !htx_add_header(htx, ist("Content-length"), clen))
1969 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001970
Christopher Faulet61cc8522020-04-20 14:54:42 +02001971 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1972 struct tcpcheck_http_hdr *hdr;
Christopher Faulet39959a72020-04-29 13:21:37 +02001973 struct ist hdr_value;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001974
Christopher Faulet61cc8522020-04-20 14:54:42 +02001975 list_for_each_entry(hdr, &send->http.hdrs, list) {
1976 chunk_reset(tmp);
1977 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1978 if (!b_data(tmp))
1979 continue;
Christopher Faulet39959a72020-04-29 13:21:37 +02001980 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1981 if (!htx_add_header(htx, hdr->name, hdr_value))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001982 goto error_htx;
Christopher Faulet39959a72020-04-29 13:21:37 +02001983 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1984 if (!http_update_authority(htx, sl, hdr_value))
1985 goto error_htx;
1986 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001988
Christopher Faulet61cc8522020-04-20 14:54:42 +02001989 }
1990 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1991 chunk_reset(tmp);
1992 httpchk_build_status_header(check->server, tmp);
1993 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1994 goto error_htx;
1995 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001996
Christopher Faulet61cc8522020-04-20 14:54:42 +02001997 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1998 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1999 !htx_add_endof(htx, HTX_BLK_EOM))
2000 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002001
Christopher Faulet61cc8522020-04-20 14:54:42 +02002002 htx_to_buf(htx, &check->bo);
2003 break;
2004 }
2005 case TCPCHK_SEND_UNDEF:
2006 /* Should never happen. */
2007 ret = TCPCHK_EVAL_STOP;
2008 goto out;
2009 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002010
Christopher Faulet6d471212020-04-22 11:09:25 +02002011
2012 if (conn->mux->snd_buf(cs, &check->bo,
2013 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02002014 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002015 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02002016 goto out;
2017 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002018 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002019 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002020 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2021 ret = TCPCHK_EVAL_WAIT;
2022 goto out;
2023 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002024
Christopher Faulet61cc8522020-04-20 14:54:42 +02002025 out:
2026 free_trash_chunk(tmp);
2027 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002028
Christopher Faulet61cc8522020-04-20 14:54:42 +02002029 error_htx:
2030 if (htx) {
2031 htx_reset(htx);
2032 htx_to_buf(htx, &check->bo);
2033 }
2034 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
2035 tcpcheck_get_step_id(check, rule));
2036 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2037 ret = TCPCHK_EVAL_STOP;
2038 goto out;
2039
2040 error_lf:
2041 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2042 tcpcheck_get_step_id(check, rule));
2043 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2044 ret = TCPCHK_EVAL_STOP;
2045 goto out;
2046
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002047}
2048
Christopher Faulet61cc8522020-04-20 14:54:42 +02002049/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2050 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2051 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2052 * TCPCHK_EVAL_STOP if an error occurred.
2053 */
2054static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002055{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002056 struct conn_stream *cs = check->cs;
2057 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002058 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002059 size_t max, read, cur_read = 0;
2060 int is_empty;
2061 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002062
Christopher Faulet61cc8522020-04-20 14:54:42 +02002063 if (check->wait_list.events & SUB_RETRY_RECV)
2064 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002065
Christopher Faulet61cc8522020-04-20 14:54:42 +02002066 if (cs->flags & CS_FL_EOS)
2067 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002068
Christopher Faulet61cc8522020-04-20 14:54:42 +02002069 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002070
Christopher Faulet61cc8522020-04-20 14:54:42 +02002071 /* prepare to detect if the mux needs more room */
2072 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002073
Christopher Faulet61cc8522020-04-20 14:54:42 +02002074 while ((cs->flags & CS_FL_RCV_MORE) ||
2075 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2076 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2077 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2078 cur_read += read;
2079 if (!read ||
2080 (cs->flags & CS_FL_WANT_ROOM) ||
2081 (--read_poll <= 0) ||
2082 (read < max && read >= global.tune.recv_enough))
2083 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002084 }
2085
Christopher Faulet61cc8522020-04-20 14:54:42 +02002086 end_recv:
2087 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2088 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2089 /* Report network errors only if we got no other data. Otherwise
2090 * we'll let the upper layers decide whether the response is OK
2091 * or not. It is very common that an RST sent by the server is
2092 * reported as an error just after the last data chunk.
2093 */
2094 goto stop;
2095 }
2096 if (!cur_read) {
2097 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2098 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2099 goto wait_more_data;
2100 }
2101 if (is_empty) {
Christopher Faulet1941bab2020-05-05 07:55:50 +02002102 int status;
2103
Christopher Faulet61cc8522020-04-20 14:54:42 +02002104 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2105 tcpcheck_get_step_id(check, rule));
2106 if (rule->comment)
2107 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002108
2109 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
2110 set_server_check_status(check, status, trash.area);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 goto stop;
2112 }
2113 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002114
2115 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002116 return ret;
2117
Christopher Faulet61cc8522020-04-20 14:54:42 +02002118 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002119 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002120 goto out;
2121
2122 wait_more_data:
2123 ret = TCPCHK_EVAL_WAIT;
2124 goto out;
2125}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002126
Christopher Faulet61cc8522020-04-20 14:54:42 +02002127/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2128 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2129 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2130 * error occurred.
2131 */
2132static 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 +02002133{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002134 struct htx *htx = htxbuf(&check->bi);
2135 struct htx_sl *sl;
2136 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002137 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002138 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet39708192020-05-05 10:47:36 +02002139 struct buffer *msg = NULL, *nbuf = NULL, *vbuf = NULL;
2140 enum healthcheck_status status = HCHK_STATUS_L7RSP;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002141 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002142 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002143
Christopher Faulet61cc8522020-04-20 14:54:42 +02002144 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002145
Christopher Faulet61cc8522020-04-20 14:54:42 +02002146 if (htx->flags & HTX_FL_PARSING_ERROR) {
2147 status = HCHK_STATUS_L7RSP;
2148 goto error;
2149 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002150
Christopher Faulet61cc8522020-04-20 14:54:42 +02002151 if (htx_is_empty(htx)) {
2152 if (last_read) {
2153 status = HCHK_STATUS_L7RSP;
2154 goto error;
2155 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002156 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002157 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002158
Christopher Faulet61cc8522020-04-20 14:54:42 +02002159 sl = http_get_stline(htx);
2160 check->code = sl->info.res.status;
2161
2162 if (check->server &&
2163 (check->server->proxy->options & PR_O_DISABLE404) &&
2164 (check->server->next_state != SRV_ST_STOPPED) &&
2165 (check->code == 404)) {
2166 /* 404 may be accepted as "stopping" only if the server was up */
2167 goto out;
2168 }
2169
2170 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2171 /* Make GCC happy ; initialize match to a failure state. */
2172 match = inverse;
2173
2174 switch (expect->type) {
2175 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002176 match = 0;
2177 for (i = 0; i < expect->codes.num; i++) {
2178 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2179 sl->info.res.status <= expect->codes.codes[i][1]) {
2180 match = 1;
2181 break;
2182 }
2183 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002184
2185 /* Set status and description in case of error */
Christopher Faulet1941bab2020-05-05 07:55:50 +02002186 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7STS);
2187 if (LIST_ISEMPTY(&expect->onerror_fmt))
2188 desc = htx_sl_res_reason(sl);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002189 break;
2190 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2191 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2192
2193 /* Set status and description in case of error */
Christopher Faulet1941bab2020-05-05 07:55:50 +02002194 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7STS);
2195 if (LIST_ISEMPTY(&expect->onerror_fmt))
2196 desc = htx_sl_res_reason(sl);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002197 break;
2198
Christopher Faulet39708192020-05-05 10:47:36 +02002199 case TCPCHK_EXPECT_HTTP_HEADER: {
2200 struct http_hdr_ctx ctx;
2201 struct ist npat, vpat, value;
2202 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
2203
2204 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
2205 nbuf = alloc_trash_chunk();
2206 if (!nbuf)
2207 goto error;
2208 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
2209 npat = ist2(b_orig(nbuf), b_data(nbuf));
2210 }
2211 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
2212 npat = expect->hdr.name;
2213
2214 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
2215 vbuf = alloc_trash_chunk();
2216 if (!vbuf)
2217 goto error;
2218 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
2219 vpat = ist2(b_orig(vbuf), b_data(vbuf));
2220 }
2221 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
2222 vpat = expect->hdr.value;
2223
2224 match = 0;
2225 ctx.blk = NULL;
2226 while (1) {
2227 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
2228 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
2229 if (!http_find_str_header(htx, npat, &ctx, full))
2230 goto end_of_match;
2231 break;
2232 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
2233 if (!http_find_pfx_header(htx, npat, &ctx, full))
2234 goto end_of_match;
2235 break;
2236 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
2237 if (!http_find_sfx_header(htx, npat, &ctx, full))
2238 goto end_of_match;
2239 break;
2240 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
2241 if (!http_find_sub_header(htx, npat, &ctx, full))
2242 goto end_of_match;
2243 break;
2244 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
2245 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
2246 goto end_of_match;
2247 break;
2248 }
2249
2250 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
2251 match = 1;
2252 goto end_of_match;
2253 }
2254
2255 value = ctx.value;
2256 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
2257 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
2258 if (isteq(value, vpat)) {
2259 match = 1;
2260 goto end_of_match;
2261 }
2262 break;
2263 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
2264 if (istlen(value) < istlen(vpat))
2265 break;
2266 value = ist2(istptr(value), istlen(vpat));
2267 if (isteq(value, vpat)) {
2268 match = 1;
2269 goto end_of_match;
2270 }
2271 break;
2272 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
2273 if (istlen(value) < istlen(vpat))
2274 break;
2275 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
2276 if (isteq(value, vpat)) {
2277 match = 1;
2278 goto end_of_match;
2279 }
2280 break;
2281 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
2282 if (isttest(istist(value, vpat))) {
2283 match = 1;
2284 goto end_of_match;
2285 }
2286 break;
2287 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
2288 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
2289 match = 1;
2290 goto end_of_match;
2291 }
2292 break;
2293 }
2294 }
2295
2296 end_of_match:
2297 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7STS);
2298 if (LIST_ISEMPTY(&expect->onerror_fmt))
2299 desc = htx_sl_res_reason(sl);
2300 break;
2301 }
2302
Christopher Faulet61cc8522020-04-20 14:54:42 +02002303 case TCPCHK_EXPECT_HTTP_BODY:
2304 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2305 chunk_reset(&trash);
2306 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2307 enum htx_blk_type type = htx_get_blk_type(blk);
2308
2309 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2310 break;
2311 if (type == HTX_BLK_DATA) {
2312 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2313 break;
2314 }
2315 }
2316
2317 if (!b_data(&trash)) {
2318 if (!last_read)
2319 goto wait_more_data;
Christopher Faulet1941bab2020-05-05 07:55:50 +02002320 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
2321 if (LIST_ISEMPTY(&expect->onerror_fmt))
2322 desc = ist("HTTP content check could not find a response body");
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323 goto error;
2324 }
2325
2326 if (!last_read &&
2327 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2328 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2329 ret = TCPCHK_EVAL_WAIT;
2330 goto out;
2331 }
2332
2333 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002334 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002335 else
2336 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2337
2338 /* Set status and description in case of error */
Christopher Faulet1941bab2020-05-05 07:55:50 +02002339 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
2340 if (LIST_ISEMPTY(&expect->onerror_fmt))
2341 desc = (inverse
2342 ? ist("HTTP check matched unwanted content")
2343 : ist("HTTP content check did not match"));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002344 break;
2345
2346 default:
2347 /* should never happen */
Christopher Faulet1941bab2020-05-05 07:55:50 +02002348 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002349 goto error;
2350 }
2351
Christopher Faulet61cc8522020-04-20 14:54:42 +02002352 /* Wait for more data on mismatch only if no minimum is defined (-1),
2353 * otherwise the absence of match is already conclusive.
2354 */
2355 if (!match && !last_read && (expect->min_recv == -1)) {
2356 ret = TCPCHK_EVAL_WAIT;
2357 goto out;
2358 }
2359
2360 if (!(match ^ inverse))
2361 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002362
2363 out:
Christopher Faulet39708192020-05-05 10:47:36 +02002364 free_trash_chunk(nbuf);
2365 free_trash_chunk(vbuf);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002366 free_trash_chunk(msg);
2367 return ret;
2368
2369 error:
2370 ret = TCPCHK_EVAL_STOP;
2371 msg = alloc_trash_chunk();
2372 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002373 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002374 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2375 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002376
2377 wait_more_data:
2378 ret = TCPCHK_EVAL_WAIT;
2379 goto out;
2380}
2381
Christopher Faulet61cc8522020-04-20 14:54:42 +02002382/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2383 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2384 * if an error occurred.
2385 */
2386static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2387{
2388 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2389 struct tcpcheck_expect *expect = &rule->expect;
2390 struct buffer *msg = NULL;
Christopher Faulet1941bab2020-05-05 07:55:50 +02002391 enum healthcheck_status status;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002392 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002393
Christopher Faulet61cc8522020-04-20 14:54:42 +02002394 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002395
Christopher Faulet61cc8522020-04-20 14:54:42 +02002396 /* The current expect might need more data than the previous one, check again
2397 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002398 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002399 if (!last_read) {
2400 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2401 (b_data(&check->bi) < istlen(expect->data))) {
2402 ret = TCPCHK_EVAL_WAIT;
2403 goto out;
2404 }
2405 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2406 ret = TCPCHK_EVAL_WAIT;
2407 goto out;
2408 }
2409 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002410
Christopher Faulet61cc8522020-04-20 14:54:42 +02002411 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2412 /* Make GCC happy ; initialize match to a failure state. */
2413 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002414
Christopher Faulet61cc8522020-04-20 14:54:42 +02002415 switch (expect->type) {
2416 case TCPCHK_EXPECT_STRING:
2417 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002418 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 +02002419 break;
2420 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002421 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 +02002422 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002423
Christopher Faulet61cc8522020-04-20 14:54:42 +02002424 case TCPCHK_EXPECT_REGEX_BINARY:
2425 chunk_reset(&trash);
2426 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002427 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002428 break;
2429 case TCPCHK_EXPECT_CUSTOM:
2430 if (expect->custom)
2431 ret = expect->custom(check, rule, last_read);
2432 goto out;
2433 default:
2434 /* Should never happen. */
2435 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002436 goto out;
2437 }
2438
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002439
Christopher Faulet61cc8522020-04-20 14:54:42 +02002440 /* Wait for more data on mismatch only if no minimum is defined (-1),
2441 * otherwise the absence of match is already conclusive.
2442 */
2443 if (!match && !last_read && (expect->min_recv == -1)) {
2444 ret = TCPCHK_EVAL_WAIT;
2445 goto out;
2446 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002447
Christopher Faulet61cc8522020-04-20 14:54:42 +02002448 /* Result as expected, next rule. */
2449 if (match ^ inverse)
2450 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002451
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002452
Christopher Faulet61cc8522020-04-20 14:54:42 +02002453 /* From this point on, we matched something we did not want, this is an error state. */
2454 ret = TCPCHK_EVAL_STOP;
2455 msg = alloc_trash_chunk();
2456 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002457 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002458
2459 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
2460 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002461 free_trash_chunk(msg);
2462 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002463
Christopher Faulet61cc8522020-04-20 14:54:42 +02002464 out:
2465 return ret;
2466}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002467
Christopher Faulet61cc8522020-04-20 14:54:42 +02002468/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2469 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2470 * waits.
2471 */
2472static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2473{
2474 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2475 struct act_rule *act_rule;
2476 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002477
Christopher Faulet61cc8522020-04-20 14:54:42 +02002478 act_rule =rule->action_kw.rule;
2479 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2480 if (act_ret != ACT_RET_CONT) {
2481 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2482 tcpcheck_get_step_id(check, rule));
2483 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2484 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002485 }
2486
Christopher Faulet61cc8522020-04-20 14:54:42 +02002487 return ret;
2488}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002489
Christopher Faulet61cc8522020-04-20 14:54:42 +02002490/* Executes a tcp-check ruleset. Note that this is called both from the
2491 * connection's wake() callback and from the check scheduling task. It returns
2492 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2493 * presenting the risk of an fd replacement.
2494 *
2495 * Please do NOT place any return statement in this function and only leave
2496 * via the out_end_tcpcheck label after setting retcode.
2497 */
2498static int tcpcheck_main(struct check *check)
2499{
2500 struct tcpcheck_rule *rule;
2501 struct conn_stream *cs = check->cs;
2502 struct connection *conn = cs_conn(cs);
2503 int must_read = 1, last_read = 0;
2504 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002505
Christopher Faulet61cc8522020-04-20 14:54:42 +02002506 /* here, we know that the check is complete or that it failed */
2507 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002508 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002509
Christopher Faulet61cc8522020-04-20 14:54:42 +02002510 /* 1- check for connection error, if any */
2511 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2512 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002513
Christopher Faulet61cc8522020-04-20 14:54:42 +02002514 /* 2- check if we are waiting for the connection establishment. It only
2515 * happens during TCPCHK_ACT_CONNECT. */
2516 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2517 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2518 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2519 if (rule->action == TCPCHK_ACT_SEND)
2520 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2521 else if (rule->action == TCPCHK_ACT_EXPECT)
2522 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2523 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002524 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002525 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002526
2527 /* 3- check for pending outgoing data. It only happens during
2528 * TCPCHK_ACT_SEND. */
2529 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2530 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002531 ret = conn->mux->snd_buf(cs, &check->bo,
2532 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002533 if (ret <= 0) {
2534 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2535 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002536 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002537 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002538 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2539 goto out;
2540 }
2541 }
2542 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002543 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002544
Christopher Faulet61cc8522020-04-20 14:54:42 +02002545 /* 4- check if a rule must be resume. It happens if check->current_step
2546 * is defined. */
2547 else if (check->current_step)
2548 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002549
Christopher Faulet61cc8522020-04-20 14:54:42 +02002550 /* 5- It is the first evaluation. We must create a session and preset
2551 * tcp-check variables */
2552 else {
2553 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002554
Christopher Faulet61cc8522020-04-20 14:54:42 +02002555 /* First evaluation, create a session */
2556 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2557 if (!check->sess) {
2558 chunk_printf(&trash, "TCPCHK error allocating check session");
2559 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2560 goto out_end_tcpcheck;
2561 }
2562 vars_init(&check->vars, SCOPE_CHECK);
2563 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002564
Christopher Faulet61cc8522020-04-20 14:54:42 +02002565 /* Preset tcp-check variables */
2566 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2567 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002568
Christopher Faulet61cc8522020-04-20 14:54:42 +02002569 memset(&smp, 0, sizeof(smp));
2570 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2571 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002572 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002573 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002574 }
2575
Christopher Faulet61cc8522020-04-20 14:54:42 +02002576 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002577
Christopher Faulet61cc8522020-04-20 14:54:42 +02002578 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2579 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002580
Christopher Faulet61cc8522020-04-20 14:54:42 +02002581 check->code = 0;
2582 switch (rule->action) {
2583 case TCPCHK_ACT_CONNECT:
2584 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002585
Christopher Faulet61cc8522020-04-20 14:54:42 +02002586 /* close but not release yet previous connection */
2587 if (check->cs) {
2588 cs_close(check->cs);
2589 retcode = -1; /* do not reuse the fd in the caller! */
2590 }
2591 eval_ret = tcpcheck_eval_connect(check, rule);
2592 must_read = 1; last_read = 0;
2593 break;
2594 case TCPCHK_ACT_SEND:
2595 check->current_step = rule;
2596 eval_ret = tcpcheck_eval_send(check, rule);
2597 must_read = 1;
2598 break;
2599 case TCPCHK_ACT_EXPECT:
2600 check->current_step = rule;
2601 if (must_read) {
2602 if (check->proxy->timeout.check)
2603 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002604
Christopher Faulet61cc8522020-04-20 14:54:42 +02002605 eval_ret = tcpcheck_eval_recv(check, rule);
2606 if (eval_ret == TCPCHK_EVAL_STOP)
2607 goto out_end_tcpcheck;
2608 else if (eval_ret == TCPCHK_EVAL_WAIT)
2609 goto out;
2610 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2611 must_read = 0;
2612 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002613
Christopher Faulet61cc8522020-04-20 14:54:42 +02002614 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2615 ? tcpcheck_eval_expect_http(check, rule, last_read)
2616 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002617
Christopher Faulet61cc8522020-04-20 14:54:42 +02002618 if (eval_ret == TCPCHK_EVAL_WAIT) {
2619 check->current_step = rule->expect.head;
2620 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2621 }
2622 break;
2623 case TCPCHK_ACT_ACTION_KW:
2624 /* Don't update the current step */
2625 eval_ret = tcpcheck_eval_action_kw(check, rule);
2626 break;
2627 default:
2628 /* Otherwise, just go to the next one and don't update
2629 * the current step
2630 */
2631 eval_ret = TCPCHK_EVAL_CONTINUE;
2632 break;
2633 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002634
Christopher Faulet61cc8522020-04-20 14:54:42 +02002635 switch (eval_ret) {
2636 case TCPCHK_EVAL_CONTINUE:
2637 break;
2638 case TCPCHK_EVAL_WAIT:
2639 goto out;
2640 case TCPCHK_EVAL_STOP:
2641 goto out_end_tcpcheck;
2642 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002643 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002644
Christopher Faulet61cc8522020-04-20 14:54:42 +02002645 /* All rules was evaluated */
2646 if (check->current_step) {
2647 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002648
Christopher Faulet61cc8522020-04-20 14:54:42 +02002649 if (rule->action == TCPCHK_ACT_EXPECT) {
2650 struct buffer *msg;
Christopher Faulet1941bab2020-05-05 07:55:50 +02002651 enum healthcheck_status status;
Willy Tarreau00149122017-10-04 18:05:01 +02002652
Christopher Faulet61cc8522020-04-20 14:54:42 +02002653 if (check->server &&
2654 (check->server->proxy->options & PR_O_DISABLE404) &&
2655 (check->server->next_state != SRV_ST_STOPPED) &&
2656 (check->code == 404)) {
2657 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2658 goto out_end_tcpcheck;
2659 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002660
Christopher Faulet61cc8522020-04-20 14:54:42 +02002661 msg = alloc_trash_chunk();
2662 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002663 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002664 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2665 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002666 free_trash_chunk(msg);
2667 }
2668 else if (rule->action == TCPCHK_ACT_CONNECT) {
2669 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002670 enum healthcheck_status status = HCHK_STATUS_L4OK;
2671#ifdef USE_OPENSSL
2672 if (conn && ssl_sock_is_ssl(conn))
2673 status = HCHK_STATUS_L6OK;
2674#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002675 set_server_check_status(check, status, msg);
2676 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002677 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002678 else
2679 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002680
Christopher Faulet61cc8522020-04-20 14:54:42 +02002681 out_end_tcpcheck:
2682 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2683 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002684
Christopher Faulet61cc8522020-04-20 14:54:42 +02002685 out:
2686 return retcode;
2687}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002688
Christopher Faulet14cd3162020-04-16 14:50:06 +02002689
Christopher Faulet61cc8522020-04-20 14:54:42 +02002690/**************************************************************************/
2691/************** Health-checks based on an external process ****************/
2692/**************************************************************************/
2693static struct list pid_list = LIST_HEAD_INIT(pid_list);
2694static struct pool_head *pool_head_pid_list;
2695__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002696
Christopher Faulet61cc8522020-04-20 14:54:42 +02002697struct extcheck_env {
2698 char *name; /* environment variable name */
2699 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2700};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002701
Christopher Faulet61cc8522020-04-20 14:54:42 +02002702/* environment variables memory requirement for different types of data */
2703#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2704 * such environment variables are not updatable. */
2705#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2706#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2707#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002708
Christopher Faulet61cc8522020-04-20 14:54:42 +02002709/* external checks environment variables */
2710enum {
2711 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002712
Christopher Faulet61cc8522020-04-20 14:54:42 +02002713 /* Proxy specific environment variables */
2714 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2715 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2716 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2717 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002718
Christopher Faulet61cc8522020-04-20 14:54:42 +02002719 /* Server specific environment variables */
2720 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2721 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2722 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2723 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2724 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2725 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002726
Christopher Faulet61cc8522020-04-20 14:54:42 +02002727 EXTCHK_SIZE
2728};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002729
Christopher Faulet61cc8522020-04-20 14:54:42 +02002730const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2731 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2732 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2733 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2734 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2735 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2736 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2737 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2738 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2739 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2740 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2741 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2742};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002743
Christopher Faulet61cc8522020-04-20 14:54:42 +02002744void block_sigchld(void)
2745{
2746 sigset_t set;
2747 sigemptyset(&set);
2748 sigaddset(&set, SIGCHLD);
2749 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2750}
Willy Tarreaube373152018-09-06 11:45:30 +02002751
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752void unblock_sigchld(void)
2753{
2754 sigset_t set;
2755 sigemptyset(&set);
2756 sigaddset(&set, SIGCHLD);
2757 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002758}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002759
Christopher Faulet61cc8522020-04-20 14:54:42 +02002760static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002761{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002762 struct pid_list *elem;
2763 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002764
Christopher Faulet61cc8522020-04-20 14:54:42 +02002765 elem = pool_alloc(pool_head_pid_list);
2766 if (!elem)
2767 return NULL;
2768 elem->pid = pid;
2769 elem->t = t;
2770 elem->exited = 0;
2771 check->curpid = elem;
2772 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002773
Christopher Faulet61cc8522020-04-20 14:54:42 +02002774 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2775 LIST_ADD(&pid_list, &elem->list);
2776 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002777
Christopher Faulet61cc8522020-04-20 14:54:42 +02002778 return elem;
2779}
Christopher Faulete5870d82020-04-15 11:32:03 +02002780
Christopher Faulet61cc8522020-04-20 14:54:42 +02002781static void pid_list_del(struct pid_list *elem)
2782{
2783 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002784
Christopher Faulet61cc8522020-04-20 14:54:42 +02002785 if (!elem)
2786 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002787
Christopher Faulet61cc8522020-04-20 14:54:42 +02002788 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2789 LIST_DEL(&elem->list);
2790 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002791
Christopher Faulet61cc8522020-04-20 14:54:42 +02002792 if (!elem->exited)
2793 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002794
Christopher Faulet61cc8522020-04-20 14:54:42 +02002795 check = elem->t->context;
2796 check->curpid = NULL;
2797 pool_free(pool_head_pid_list, elem);
2798}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002799
Christopher Faulet61cc8522020-04-20 14:54:42 +02002800/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2801static void pid_list_expire(pid_t pid, int status)
2802{
2803 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002804
Christopher Faulet61cc8522020-04-20 14:54:42 +02002805 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2806 list_for_each_entry(elem, &pid_list, list) {
2807 if (elem->pid == pid) {
2808 elem->t->expire = now_ms;
2809 elem->status = status;
2810 elem->exited = 1;
2811 task_wakeup(elem->t, TASK_WOKEN_IO);
2812 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002813 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002814 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002815 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2816}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002817
Christopher Faulet61cc8522020-04-20 14:54:42 +02002818static void sigchld_handler(struct sig_handler *sh)
2819{
2820 pid_t pid;
2821 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002822
Christopher Faulet61cc8522020-04-20 14:54:42 +02002823 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2824 pid_list_expire(pid, status);
2825}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002826
Christopher Faulet61cc8522020-04-20 14:54:42 +02002827static int init_pid_list(void)
2828{
2829 if (pool_head_pid_list != NULL)
2830 /* Nothing to do */
2831 return 0;
2832
2833 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2834 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2835 strerror(errno));
2836 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002837 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002838
Christopher Faulet61cc8522020-04-20 14:54:42 +02002839 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2840 if (pool_head_pid_list == NULL) {
2841 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2842 strerror(errno));
2843 return 1;
2844 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002845
Christopher Faulet61cc8522020-04-20 14:54:42 +02002846 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002847}
2848
Christopher Faulet61cc8522020-04-20 14:54:42 +02002849/* helper macro to set an environment variable and jump to a specific label on failure. */
2850#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002851
Christopher Faulet61cc8522020-04-20 14:54:42 +02002852/*
2853 * helper function to allocate enough memory to store an environment variable.
2854 * It will also check that the environment variable is updatable, and silently
2855 * fail if not.
2856 */
2857static int extchk_setenv(struct check *check, int idx, const char *value)
2858{
2859 int len, ret;
2860 char *envname;
2861 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002862
Christopher Faulet61cc8522020-04-20 14:54:42 +02002863 if (idx < 0 || idx >= EXTCHK_SIZE) {
2864 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2865 return 1;
2866 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002867
Christopher Faulet61cc8522020-04-20 14:54:42 +02002868 envname = extcheck_envs[idx].name;
2869 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002870
Christopher Faulet61cc8522020-04-20 14:54:42 +02002871 /* Check if the environment variable is already set, and silently reject
2872 * the update if this one is not updatable. */
2873 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2874 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002875
Christopher Faulet61cc8522020-04-20 14:54:42 +02002876 /* Instead of sending NOT_USED, sending an empty value is preferable */
2877 if (strcmp(value, "NOT_USED") == 0) {
2878 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002879 }
2880
Christopher Faulet61cc8522020-04-20 14:54:42 +02002881 len = strlen(envname) + 1;
2882 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2883 len += strlen(value);
2884 else
2885 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002886
Christopher Faulet61cc8522020-04-20 14:54:42 +02002887 if (!check->envp[idx])
2888 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002889
Christopher Faulet61cc8522020-04-20 14:54:42 +02002890 if (!check->envp[idx]) {
2891 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2892 return 1;
2893 }
2894 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2895 if (ret < 0) {
2896 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2897 return 1;
2898 }
2899 else if (ret > len) {
2900 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2901 return 1;
2902 }
2903 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002904}
2905
Christopher Faulet61cc8522020-04-20 14:54:42 +02002906static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002907{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002908 struct server *s = check->server;
2909 struct proxy *px = s->proxy;
2910 struct listener *listener = NULL, *l;
2911 int i;
2912 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2913 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002914
Christopher Faulet61cc8522020-04-20 14:54:42 +02002915 list_for_each_entry(l, &px->conf.listeners, by_fe)
2916 /* Use the first INET, INET6 or UNIX listener */
2917 if (l->addr.ss_family == AF_INET ||
2918 l->addr.ss_family == AF_INET6 ||
2919 l->addr.ss_family == AF_UNIX) {
2920 listener = l;
2921 break;
2922 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002923
Christopher Faulet61cc8522020-04-20 14:54:42 +02002924 check->curpid = NULL;
2925 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2926 if (!check->envp) {
2927 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2928 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002929 }
2930
Christopher Faulet61cc8522020-04-20 14:54:42 +02002931 check->argv = calloc(6, sizeof(char *));
2932 if (!check->argv) {
2933 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2934 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002935 }
2936
Christopher Faulet61cc8522020-04-20 14:54:42 +02002937 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002938
Christopher Faulet61cc8522020-04-20 14:54:42 +02002939 if (!listener) {
2940 check->argv[1] = strdup("NOT_USED");
2941 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002942 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002943 else if (listener->addr.ss_family == AF_INET ||
2944 listener->addr.ss_family == AF_INET6) {
2945 addr_to_str(&listener->addr, buf, sizeof(buf));
2946 check->argv[1] = strdup(buf);
2947 port_to_str(&listener->addr, buf, sizeof(buf));
2948 check->argv[2] = strdup(buf);
2949 }
2950 else if (listener->addr.ss_family == AF_UNIX) {
2951 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002952
Christopher Faulet61cc8522020-04-20 14:54:42 +02002953 un = (struct sockaddr_un *)&listener->addr;
2954 check->argv[1] = strdup(un->sun_path);
2955 check->argv[2] = strdup("NOT_USED");
2956 }
2957 else {
2958 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2959 goto err;
2960 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002961
Christopher Faulet61cc8522020-04-20 14:54:42 +02002962 if (!check->argv[1] || !check->argv[2]) {
2963 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2964 goto err;
2965 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002966
Christopher Faulet61cc8522020-04-20 14:54:42 +02002967 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2968 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2969 if (!check->argv[3] || !check->argv[4]) {
2970 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2971 goto err;
2972 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002973
Christopher Faulet61cc8522020-04-20 14:54:42 +02002974 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2975 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2976 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002977
Christopher Faulet61cc8522020-04-20 14:54:42 +02002978 for (i = 0; i < 5; i++) {
2979 if (!check->argv[i]) {
2980 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2981 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002982 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002983 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002984
Christopher Faulet61cc8522020-04-20 14:54:42 +02002985 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2986 /* Add proxy environment variables */
2987 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2988 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2989 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2990 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2991 /* Add server environment variables */
2992 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2993 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2994 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2995 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2996 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2997 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002998
Christopher Faulet61cc8522020-04-20 14:54:42 +02002999 /* Ensure that we don't leave any hole in check->envp */
3000 for (i = 0; i < EXTCHK_SIZE; i++)
3001 if (!check->envp[i])
3002 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02003003
Christopher Faulet61cc8522020-04-20 14:54:42 +02003004 return 1;
3005err:
3006 if (check->envp) {
3007 for (i = 0; i < EXTCHK_SIZE; i++)
3008 free(check->envp[i]);
3009 free(check->envp);
3010 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02003011 }
3012
Christopher Faulet61cc8522020-04-20 14:54:42 +02003013 if (check->argv) {
3014 for (i = 1; i < 5; i++)
3015 free(check->argv[i]);
3016 free(check->argv);
3017 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02003018 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003019 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003020}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003021
Christopher Faulet61cc8522020-04-20 14:54:42 +02003022/*
3023 * establish a server health-check that makes use of a process.
3024 *
3025 * It can return one of :
3026 * - SF_ERR_NONE if everything's OK
3027 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
3028 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
3029 *
3030 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003031 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02003032static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003033{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003034 char buf[256];
3035 struct check *check = t->context;
3036 struct server *s = check->server;
3037 struct proxy *px = s->proxy;
3038 int status;
3039 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003040
Christopher Faulet61cc8522020-04-20 14:54:42 +02003041 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003042
Christopher Faulet61cc8522020-04-20 14:54:42 +02003043 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003044
Christopher Faulet61cc8522020-04-20 14:54:42 +02003045 pid = fork();
3046 if (pid < 0) {
3047 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
3048 (global.tune.options & GTUNE_INSECURE_FORK) ?
3049 "" : " (likely caused by missing 'insecure-fork-wanted')",
3050 strerror(errno));
3051 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003052 goto out;
3053 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003054 if (pid == 0) {
3055 /* Child */
3056 extern char **environ;
3057 struct rlimit limit;
3058 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003059
Christopher Faulet61cc8522020-04-20 14:54:42 +02003060 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
3061 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003062
Christopher Faulet61cc8522020-04-20 14:54:42 +02003063 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003064
Christopher Faulet61cc8522020-04-20 14:54:42 +02003065 /* restore the initial FD limits */
3066 limit.rlim_cur = rlim_fd_cur_at_boot;
3067 limit.rlim_max = rlim_fd_max_at_boot;
3068 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
3069 getrlimit(RLIMIT_NOFILE, &limit);
3070 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
3071 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
3072 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
3073 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003074
Christopher Faulet61cc8522020-04-20 14:54:42 +02003075 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003076
Christopher Faulet61cc8522020-04-20 14:54:42 +02003077 /* Update some environment variables and command args: curconn, server addr and server port */
3078 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003079
Christopher Faulet61cc8522020-04-20 14:54:42 +02003080 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
3081 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003082
Christopher Faulet61cc8522020-04-20 14:54:42 +02003083 *check->argv[4] = 0;
3084 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
3085 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
3086 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003087
Christopher Faulet61cc8522020-04-20 14:54:42 +02003088 haproxy_unblock_signals();
3089 execvp(px->check_command, check->argv);
3090 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
3091 strerror(errno));
3092 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003093 }
3094
Christopher Faulet61cc8522020-04-20 14:54:42 +02003095 /* Parent */
3096 if (check->result == CHK_RES_UNKNOWN) {
3097 if (pid_list_add(pid, t) != NULL) {
3098 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3099
3100 if (px->timeout.check && px->timeout.connect) {
3101 int t_con = tick_add(now_ms, px->timeout.connect);
3102 t->expire = tick_first(t->expire, t_con);
3103 }
3104 status = SF_ERR_NONE;
3105 goto out;
3106 }
3107 else {
3108 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
3109 }
3110 kill(pid, SIGTERM); /* process creation error */
3111 }
3112 else
3113 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
3114
3115out:
3116 unblock_sigchld();
3117 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003118}
3119
Christopher Faulet61cc8522020-04-20 14:54:42 +02003120/*
3121 * manages a server health-check that uses an external process. Returns
3122 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003123 *
3124 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02003125 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003126 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02003127static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003128{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003129 struct check *check = context;
3130 struct server *s = check->server;
3131 int rv;
3132 int ret;
3133 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003134
Christopher Faulet61cc8522020-04-20 14:54:42 +02003135 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3136 if (!(check->state & CHK_ST_INPROGRESS)) {
3137 /* no check currently running */
3138 if (!expired) /* woke up too early */
3139 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003140
Christopher Faulet61cc8522020-04-20 14:54:42 +02003141 /* we don't send any health-checks when the proxy is
3142 * stopped, the server should not be checked or the check
3143 * is disabled.
3144 */
3145 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3146 s->proxy->state == PR_STSTOPPED)
3147 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003148
Christopher Faulet61cc8522020-04-20 14:54:42 +02003149 /* we'll initiate a new check */
3150 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003151
Christopher Faulet61cc8522020-04-20 14:54:42 +02003152 check->state |= CHK_ST_INPROGRESS;
3153
3154 ret = connect_proc_chk(t);
3155 if (ret == SF_ERR_NONE) {
3156 /* the process was forked, we allow up to min(inter,
3157 * timeout.connect) for it to report its status, but
3158 * only when timeout.check is set as it may be to short
3159 * for a full check otherwise.
3160 */
3161 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3162
3163 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3164 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3165 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003166 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003167 task_set_affinity(t, tid_bit);
3168 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003169 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003170
Christopher Faulet61cc8522020-04-20 14:54:42 +02003171 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003172
Christopher Faulet61cc8522020-04-20 14:54:42 +02003173 check->state &= ~CHK_ST_INPROGRESS;
3174 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003175
Christopher Faulet61cc8522020-04-20 14:54:42 +02003176 /* we allow up to min(inter, timeout.connect) for a connection
3177 * to establish but only when timeout.check is set
3178 * as it may be to short for a full check otherwise
3179 */
3180 while (tick_is_expired(t->expire, now_ms)) {
3181 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003182
Christopher Faulet61cc8522020-04-20 14:54:42 +02003183 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3184 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003185
Christopher Faulet61cc8522020-04-20 14:54:42 +02003186 if (s->proxy->timeout.check)
3187 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003188 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003189 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003190 else {
3191 /* there was a test running.
3192 * First, let's check whether there was an uncaught error,
3193 * which can happen on connect timeout or error.
3194 */
3195 if (check->result == CHK_RES_UNKNOWN) {
3196 /* good connection is enough for pure TCP check */
3197 struct pid_list *elem = check->curpid;
3198 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003199
Christopher Faulet61cc8522020-04-20 14:54:42 +02003200 if (elem->exited) {
3201 status = elem->status; /* Save in case the process exits between use below */
3202 if (!WIFEXITED(status))
3203 check->code = -1;
3204 else
3205 check->code = WEXITSTATUS(status);
3206 if (!WIFEXITED(status) || WEXITSTATUS(status))
3207 status = HCHK_STATUS_PROCERR;
3208 else
3209 status = HCHK_STATUS_PROCOK;
3210 } else if (expired) {
3211 status = HCHK_STATUS_PROCTOUT;
3212 ha_warning("kill %d\n", (int)elem->pid);
3213 kill(elem->pid, SIGTERM);
3214 }
3215 set_server_check_status(check, status, NULL);
3216 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003217
Christopher Faulet61cc8522020-04-20 14:54:42 +02003218 if (check->result == CHK_RES_FAILED) {
3219 /* a failure or timeout detected */
3220 check_notify_failure(check);
3221 }
3222 else if (check->result == CHK_RES_CONDPASS) {
3223 /* check is OK but asks for stopping mode */
3224 check_notify_stopping(check);
3225 }
3226 else if (check->result == CHK_RES_PASSED) {
3227 /* a success was detected */
3228 check_notify_success(check);
3229 }
3230 task_set_affinity(t, 1);
3231 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003232
Christopher Faulet61cc8522020-04-20 14:54:42 +02003233 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003234
Christopher Faulet61cc8522020-04-20 14:54:42 +02003235 rv = 0;
3236 if (global.spread_checks > 0) {
3237 rv = srv_getinter(check) * global.spread_checks / 100;
3238 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3239 }
3240 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3241 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003242
Christopher Faulet61cc8522020-04-20 14:54:42 +02003243 reschedule:
3244 while (tick_is_expired(t->expire, now_ms))
3245 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003246
Christopher Faulet61cc8522020-04-20 14:54:42 +02003247 out_unlock:
3248 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3249 return t;
3250}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003251
Baptiste Assmann248f1172018-03-01 21:49:01 +01003252
Christopher Faulet61cc8522020-04-20 14:54:42 +02003253/**************************************************************************/
3254/***************** Health-checks based on connections *********************/
3255/**************************************************************************/
3256/* This function is used only for server health-checks. It handles connection
3257 * status updates including errors. If necessary, it wakes the check task up.
3258 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3259 * connection (eg: reconnect). It relies on tcpcheck_main().
3260 */
3261static int wake_srv_chk(struct conn_stream *cs)
3262{
3263 struct connection *conn = cs->conn;
3264 struct check *check = cs->data;
3265 struct email_alertq *q = container_of(check, typeof(*q), check);
3266 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003267
Christopher Faulet61cc8522020-04-20 14:54:42 +02003268 if (check->server)
3269 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3270 else
3271 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003272
Christopher Faulet61cc8522020-04-20 14:54:42 +02003273 /* we may have to make progress on the TCP checks */
3274 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003275
Christopher Faulet61cc8522020-04-20 14:54:42 +02003276 cs = check->cs;
3277 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003278
Christopher Faulet61cc8522020-04-20 14:54:42 +02003279 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3280 /* We may get error reports bypassing the I/O handlers, typically
3281 * the case when sending a pure TCP check which fails, then the I/O
3282 * handlers above are not called. This is completely handled by the
3283 * main processing task so let's simply wake it up. If we get here,
3284 * we expect errno to still be valid.
3285 */
3286 chk_report_conn_err(check, errno, 0);
3287 task_wakeup(check->task, TASK_WOKEN_IO);
3288 }
3289
3290 if (check->result != CHK_RES_UNKNOWN) {
3291 /* Check complete or aborted. If connection not yet closed do it
3292 * now and wake the check task up to be sure the result is
3293 * handled ASAP. */
3294 conn_sock_drain(conn);
3295 cs_close(cs);
3296 ret = -1;
3297 /* We may have been scheduled to run, and the
3298 * I/O handler expects to have a cs, so remove
3299 * the tasklet
3300 */
3301 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3302 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003303 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003304
3305 if (check->server)
3306 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003307 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003308 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003309
Christopher Faulet61cc8522020-04-20 14:54:42 +02003310 /* if a connection got replaced, we must absolutely prevent the connection
3311 * handler from touching its fd, and perform the FD polling updates ourselves
3312 */
3313 if (ret < 0)
3314 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003315
Christopher Faulet61cc8522020-04-20 14:54:42 +02003316 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003317}
3318
Christopher Faulet61cc8522020-04-20 14:54:42 +02003319/* This function checks if any I/O is wanted, and if so, attempts to do so */
3320static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003321{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003322 struct check *check = ctx;
3323 struct conn_stream *cs = check->cs;
3324 struct email_alertq *q = container_of(check, typeof(*q), check);
3325 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003326
Christopher Faulet61cc8522020-04-20 14:54:42 +02003327 if (!(check->wait_list.events & SUB_RETRY_SEND))
3328 ret = wake_srv_chk(cs);
3329 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3330 if (check->server)
3331 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3332 else
3333 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003334
Christopher Faulet61cc8522020-04-20 14:54:42 +02003335 if (unlikely(check->result == CHK_RES_FAILED)) {
3336 /* collect possible new errors */
3337 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3338 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003339
Christopher Faulet61cc8522020-04-20 14:54:42 +02003340 /* Reset the check buffer... */
3341 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003342
Christopher Faulet61cc8522020-04-20 14:54:42 +02003343 /* Close the connection... We still attempt to nicely close if,
3344 * for instance, SSL needs to send a "close notify." Later, we perform
3345 * a hard close and reset the connection if some data are pending,
3346 * otherwise we end up with many TIME_WAITs and eat all the source port
3347 * range quickly. To avoid sending RSTs all the time, we first try to
3348 * drain pending data.
3349 */
3350 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3351 * connection, to make sure cs_shutw() will not lead to a shutdown()
3352 * that would provoke TIME_WAITs.
3353 */
3354 cs_shutr(cs, CS_SHR_DRAIN);
3355 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003356
Christopher Faulet61cc8522020-04-20 14:54:42 +02003357 /* OK, let's not stay here forever */
3358 if (check->result == CHK_RES_FAILED)
3359 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003360
Christopher Faulet61cc8522020-04-20 14:54:42 +02003361 task_wakeup(t, TASK_WOKEN_IO);
3362 }
3363
3364 if (check->server)
3365 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3366 else
3367 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003368 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003369 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003370}
3371
Christopher Faulet61cc8522020-04-20 14:54:42 +02003372/* manages a server health-check that uses a connection. Returns
3373 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3374 *
3375 * Please do NOT place any return statement in this function and only leave
3376 * via the out_unlock label.
3377 */
3378static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003379{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003380 struct check *check = context;
3381 struct proxy *proxy = check->proxy;
3382 struct conn_stream *cs = check->cs;
3383 struct connection *conn = cs_conn(cs);
3384 int rv;
3385 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003386
Christopher Faulet61cc8522020-04-20 14:54:42 +02003387 if (check->server)
3388 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3389 if (!(check->state & CHK_ST_INPROGRESS)) {
3390 /* no check currently running */
3391 if (!expired) /* woke up too early */
3392 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003393
Christopher Faulet61cc8522020-04-20 14:54:42 +02003394 /* we don't send any health-checks when the proxy is
3395 * stopped, the server should not be checked or the check
3396 * is disabled.
3397 */
3398 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3399 proxy->state == PR_STSTOPPED)
3400 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003401
Christopher Faulet61cc8522020-04-20 14:54:42 +02003402 /* we'll initiate a new check */
3403 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003404
Christopher Faulet61cc8522020-04-20 14:54:42 +02003405 check->state |= CHK_ST_INPROGRESS;
3406 b_reset(&check->bi);
3407 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003408
Christopher Faulet61cc8522020-04-20 14:54:42 +02003409 task_set_affinity(t, tid_bit);
3410 cs = check->cs;
3411 conn = cs_conn(cs);
3412 if (!conn) {
3413 check->current_step = NULL;
3414 tcpcheck_main(check);
3415 goto out_unlock;
3416 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003417
Christopher Faulet61cc8522020-04-20 14:54:42 +02003418 conn->flags |= CO_FL_ERROR;
3419 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003420
Christopher Faulet61cc8522020-04-20 14:54:42 +02003421 /* here, we have seen a synchronous error, no fd was allocated */
3422 task_set_affinity(t, MAX_THREADS_MASK);
3423 if (cs) {
3424 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003425 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003426 /* We may have been scheduled to run, and the
3427 * I/O handler expects to have a cs, so remove
3428 * the tasklet
3429 */
3430 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3431 cs_destroy(cs);
3432 cs = check->cs = NULL;
3433 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003434 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003435
Christopher Faulet61cc8522020-04-20 14:54:42 +02003436 check->state &= ~CHK_ST_INPROGRESS;
3437 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003438
Christopher Faulet61cc8522020-04-20 14:54:42 +02003439 /* we allow up to min(inter, timeout.connect) for a connection
3440 * to establish but only when timeout.check is set
3441 * as it may be to short for a full check otherwise
3442 */
3443 while (tick_is_expired(t->expire, now_ms)) {
3444 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003445
Christopher Faulet61cc8522020-04-20 14:54:42 +02003446 t_con = tick_add(t->expire, proxy->timeout.connect);
3447 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3448 if (proxy->timeout.check)
3449 t->expire = tick_first(t->expire, t_con);
3450 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003451 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003452 else {
3453 /* there was a test running.
3454 * First, let's check whether there was an uncaught error,
3455 * which can happen on connect timeout or error.
3456 */
3457 if (check->result == CHK_RES_UNKNOWN) {
3458 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3459 chk_report_conn_err(check, 0, expired);
3460 }
3461 else
3462 goto out_unlock; /* timeout not reached, wait again */
3463 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003464
Christopher Faulet61cc8522020-04-20 14:54:42 +02003465 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003466
Christopher Faulet61cc8522020-04-20 14:54:42 +02003467 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003468
Christopher Faulet61cc8522020-04-20 14:54:42 +02003469 if (conn && conn->xprt) {
3470 /* The check was aborted and the connection was not yet closed.
3471 * This can happen upon timeout, or when an external event such
3472 * as a failed response coupled with "observe layer7" caused the
3473 * server state to be suddenly changed.
3474 */
3475 conn_sock_drain(conn);
3476 cs_close(cs);
3477 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003478
Christopher Faulet61cc8522020-04-20 14:54:42 +02003479 if (cs) {
3480 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003481 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003482 /* We may have been scheduled to run, and the
3483 * I/O handler expects to have a cs, so remove
3484 * the tasklet
3485 */
3486 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3487 cs_destroy(cs);
3488 cs = check->cs = NULL;
3489 conn = NULL;
3490 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003491
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003492 if (check->sess != NULL) {
3493 vars_prune(&check->vars, check->sess, NULL);
3494 session_free(check->sess);
3495 check->sess = NULL;
3496 }
3497
Christopher Faulet61cc8522020-04-20 14:54:42 +02003498 if (check->server) {
3499 if (check->result == CHK_RES_FAILED) {
3500 /* a failure or timeout detected */
3501 check_notify_failure(check);
3502 }
3503 else if (check->result == CHK_RES_CONDPASS) {
3504 /* check is OK but asks for stopping mode */
3505 check_notify_stopping(check);
3506 }
3507 else if (check->result == CHK_RES_PASSED) {
3508 /* a success was detected */
3509 check_notify_success(check);
3510 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003511 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003512 task_set_affinity(t, MAX_THREADS_MASK);
3513 check->state &= ~CHK_ST_INPROGRESS;
3514
3515 if (check->server) {
3516 rv = 0;
3517 if (global.spread_checks > 0) {
3518 rv = srv_getinter(check) * global.spread_checks / 100;
3519 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3520 }
3521 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003522 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003523 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003524
Christopher Faulet61cc8522020-04-20 14:54:42 +02003525 reschedule:
3526 while (tick_is_expired(t->expire, now_ms))
3527 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3528 out_unlock:
3529 if (check->server)
3530 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3531 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003532}
3533
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003534
Christopher Faulet61cc8522020-04-20 14:54:42 +02003535/**************************************************************************/
3536/******************* Internals to parse tcp-check rules *******************/
3537/**************************************************************************/
3538struct action_kw_list tcp_check_keywords = {
3539 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3540};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003541
Christopher Faulet61cc8522020-04-20 14:54:42 +02003542/* Return the struct action_kw associated to a keyword */
3543static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003544{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003545 return action_lookup(&tcp_check_keywords.list, kw);
3546}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003547
Christopher Faulet61cc8522020-04-20 14:54:42 +02003548static void action_kw_tcp_check_build_list(struct buffer *chk)
3549{
3550 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003551}
3552
Christopher Faulet61cc8522020-04-20 14:54:42 +02003553/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3554 * returned on error.
3555 */
3556static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3557 struct list *rules, struct action_kw *kw,
3558 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003559{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003560 struct tcpcheck_rule *chk = NULL;
3561 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003562
Christopher Faulet61cc8522020-04-20 14:54:42 +02003563 actrule = calloc(1, sizeof(*actrule));
3564 if (!actrule) {
3565 memprintf(errmsg, "out of memory");
3566 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003567 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003568 actrule->kw = kw;
3569 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003570
Christopher Faulet61cc8522020-04-20 14:54:42 +02003571 cur_arg++;
3572 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3573 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3574 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003575 }
3576
Christopher Faulet61cc8522020-04-20 14:54:42 +02003577 chk = calloc(1, sizeof(*chk));
3578 if (!chk) {
3579 memprintf(errmsg, "out of memory");
3580 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003581 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003582 chk->action = TCPCHK_ACT_ACTION_KW;
3583 chk->action_kw.rule = actrule;
3584 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003585
3586 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003587 free(actrule);
3588 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003589}
3590
Christopher Faulet61cc8522020-04-20 14:54:42 +02003591/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3592 * returned on error.
3593 */
3594static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3595 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003596{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003597 struct tcpcheck_rule *chk = NULL;
3598 struct sockaddr_storage *sk = NULL;
3599 char *comment = NULL, *sni = NULL, *alpn = NULL;
3600 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003601 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003602 unsigned short conn_opts = 0;
3603 long port = 0;
3604 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003605
Christopher Faulet61cc8522020-04-20 14:54:42 +02003606 list_for_each_entry(chk, rules, list) {
3607 if (chk->action == TCPCHK_ACT_CONNECT)
3608 break;
3609 if (chk->action == TCPCHK_ACT_COMMENT ||
3610 chk->action == TCPCHK_ACT_ACTION_KW ||
3611 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3612 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003613
Christopher Faulet61cc8522020-04-20 14:54:42 +02003614 memprintf(errmsg, "first step MUST also be a 'connect', "
3615 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3616 "when there is a 'connect' step in the tcp-check ruleset");
3617 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003618 }
3619
Christopher Faulet61cc8522020-04-20 14:54:42 +02003620 cur_arg++;
3621 while (*(args[cur_arg])) {
3622 if (strcmp(args[cur_arg], "default") == 0)
3623 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3624 else if (strcmp(args[cur_arg], "addr") == 0) {
3625 int port1, port2;
3626 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003627
Christopher Faulet61cc8522020-04-20 14:54:42 +02003628 if (!*(args[cur_arg+1])) {
3629 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3630 goto error;
3631 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003632
Christopher Faulet61cc8522020-04-20 14:54:42 +02003633 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3634 if (!sk) {
3635 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3636 goto error;
3637 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003638
Christopher Faulet61cc8522020-04-20 14:54:42 +02003639 proto = protocol_by_family(sk->ss_family);
3640 if (!proto || !proto->connect) {
3641 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3642 args[cur_arg]);
3643 goto error;
3644 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003645
Christopher Faulet61cc8522020-04-20 14:54:42 +02003646 if (port1 != port2) {
3647 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3648 args[cur_arg], args[cur_arg+1]);
3649 goto error;
3650 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651
Christopher Faulet61cc8522020-04-20 14:54:42 +02003652 cur_arg++;
3653 }
3654 else if (strcmp(args[cur_arg], "port") == 0) {
3655 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656
Christopher Faulet61cc8522020-04-20 14:54:42 +02003657 if (!*(args[cur_arg+1])) {
3658 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3659 goto error;
3660 }
3661 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003662
Christopher Faulet61cc8522020-04-20 14:54:42 +02003663 port = 0;
3664 release_sample_expr(port_expr);
3665 p = args[cur_arg]; end = p + strlen(p);
3666 port = read_uint(&p, end);
3667 if (p != end) {
3668 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003669
Christopher Faulet61cc8522020-04-20 14:54:42 +02003670 px->conf.args.ctx = ARGC_SRV;
3671 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3672 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003673
Christopher Faulet61cc8522020-04-20 14:54:42 +02003674 if (!port_expr) {
3675 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3676 goto error;
3677 }
3678 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3679 memprintf(errmsg, "error detected while parsing port expression : "
3680 " fetch method '%s' extracts information from '%s', "
3681 "none of which is available here.\n",
3682 args[cur_arg], sample_src_names(port_expr->fetch->use));
3683 goto error;
3684 }
3685 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3686 }
3687 else if (port > 65535 || port < 1) {
3688 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3689 args[cur_arg]);
3690 goto error;
3691 }
3692 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003693 else if (strcmp(args[cur_arg], "proto") == 0) {
3694 if (!*(args[cur_arg+1])) {
3695 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3696 goto error;
3697 }
3698 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3699 if (!mux_proto) {
3700 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3701 goto error;
3702 }
3703 cur_arg++;
3704 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003705 else if (strcmp(args[cur_arg], "comment") == 0) {
3706 if (!*(args[cur_arg+1])) {
3707 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3708 goto error;
3709 }
3710 cur_arg++;
3711 free(comment);
3712 comment = strdup(args[cur_arg]);
3713 if (!comment) {
3714 memprintf(errmsg, "out of memory");
3715 goto error;
3716 }
3717 }
3718 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3719 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3720 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3721 conn_opts |= TCPCHK_OPT_SOCKS4;
3722 else if (strcmp(args[cur_arg], "linger") == 0)
3723 conn_opts |= TCPCHK_OPT_LINGER;
3724#ifdef USE_OPENSSL
3725 else if (strcmp(args[cur_arg], "ssl") == 0) {
3726 px->options |= PR_O_TCPCHK_SSL;
3727 conn_opts |= TCPCHK_OPT_SSL;
3728 }
3729 else if (strcmp(args[cur_arg], "sni") == 0) {
3730 if (!*(args[cur_arg+1])) {
3731 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3732 goto error;
3733 }
3734 cur_arg++;
3735 free(sni);
3736 sni = strdup(args[cur_arg]);
3737 if (!sni) {
3738 memprintf(errmsg, "out of memory");
3739 goto error;
3740 }
3741 }
3742 else if (strcmp(args[cur_arg], "alpn") == 0) {
3743#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3744 free(alpn);
3745 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3746 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3747 goto error;
3748 }
3749 cur_arg++;
3750#else
3751 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003752 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003753#endif
3754 }
3755#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003756
Christopher Faulet61cc8522020-04-20 14:54:42 +02003757 else {
3758 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3759#ifdef USE_OPENSSL
3760 ", 'ssl', 'sni', 'alpn'"
3761#endif /* USE_OPENSSL */
3762 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3763 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003764 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003765 }
3766 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003767 }
3768
Christopher Faulet61cc8522020-04-20 14:54:42 +02003769 chk = calloc(1, sizeof(*chk));
3770 if (!chk) {
3771 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003772 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003773 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003774 chk->action = TCPCHK_ACT_CONNECT;
3775 chk->comment = comment;
3776 chk->connect.port = port;
3777 chk->connect.options = conn_opts;
3778 chk->connect.sni = sni;
3779 chk->connect.alpn = alpn;
3780 chk->connect.alpn_len= alpn_len;
3781 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003782 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003783 if (sk)
3784 chk->connect.addr = *sk;
3785 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003786
Christopher Faulet61cc8522020-04-20 14:54:42 +02003787 error:
3788 free(alpn);
3789 free(sni);
3790 free(comment);
3791 release_sample_expr(port_expr);
3792 return NULL;
3793}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003794
Christopher Faulet61cc8522020-04-20 14:54:42 +02003795/* Parses and creates a tcp-check send rule. NULL is returned on error */
3796static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3797 const char *file, int line, char **errmsg)
3798{
3799 struct tcpcheck_rule *chk = NULL;
3800 char *comment = NULL, *data = NULL;
3801 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003802
Christopher Faulet61cc8522020-04-20 14:54:42 +02003803 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3804 if (!*(args[cur_arg+1])) {
3805 memprintf(errmsg, "'%s' expects a %s as argument",
3806 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003807 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003808 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003809
Christopher Faulet61cc8522020-04-20 14:54:42 +02003810 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003811
Christopher Faulet61cc8522020-04-20 14:54:42 +02003812 cur_arg += 2;
3813 while (*(args[cur_arg])) {
3814 if (strcmp(args[cur_arg], "comment") == 0) {
3815 if (!*(args[cur_arg+1])) {
3816 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3817 goto error;
3818 }
3819 cur_arg++;
3820 free(comment);
3821 comment = strdup(args[cur_arg]);
3822 if (!comment) {
3823 memprintf(errmsg, "out of memory");
3824 goto error;
3825 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003827 else if (strcmp(args[cur_arg], "log-format") == 0) {
3828 if (type == TCPCHK_SEND_BINARY)
3829 type = TCPCHK_SEND_BINARY_LF;
3830 else if (type == TCPCHK_SEND_STRING)
3831 type = TCPCHK_SEND_STRING_LF;
3832 }
3833 else {
3834 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3835 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003836 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003837 }
3838 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003839 }
3840
Christopher Faulet61cc8522020-04-20 14:54:42 +02003841 chk = calloc(1, sizeof(*chk));
3842 if (!chk) {
3843 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003844 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003845 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003846 chk->action = TCPCHK_ACT_SEND;
3847 chk->comment = comment;
3848 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003849
Christopher Faulet61cc8522020-04-20 14:54:42 +02003850 switch (chk->send.type) {
3851 case TCPCHK_SEND_STRING:
3852 chk->send.data = ist2(strdup(data), strlen(data));
3853 if (!isttest(chk->send.data)) {
3854 memprintf(errmsg, "out of memory");
3855 goto error;
3856 }
3857 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003858 case TCPCHK_SEND_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02003859 int len = chk->send.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003860 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003861 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3862 goto error;
3863 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003864 chk->send.data.len = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003865 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003866 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003867 case TCPCHK_SEND_STRING_LF:
3868 case TCPCHK_SEND_BINARY_LF:
3869 LIST_INIT(&chk->send.fmt);
3870 px->conf.args.ctx = ARGC_SRV;
3871 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3872 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3873 goto error;
3874 }
3875 break;
3876 case TCPCHK_SEND_HTTP:
3877 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003878 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003879 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003880
Christopher Faulet61cc8522020-04-20 14:54:42 +02003881 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003882
Christopher Faulet61cc8522020-04-20 14:54:42 +02003883 error:
3884 free(chk);
3885 free(comment);
3886 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003887}
3888
Christopher Faulet61cc8522020-04-20 14:54:42 +02003889/* Parses and creates a http-check send rule. NULL is returned on error */
3890static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3891 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003892{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003893 struct tcpcheck_rule *chk = NULL;
3894 struct tcpcheck_http_hdr *hdr = NULL;
3895 struct http_hdr hdrs[global.tune.max_http_hdr];
3896 char *meth = NULL, *uri = NULL, *vsn = NULL;
3897 char *body = NULL, *comment = NULL;
3898 unsigned int flags = 0;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003899 int i = 0, host_hdr = -1;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003900
3901 cur_arg++;
3902 while (*(args[cur_arg])) {
3903 if (strcmp(args[cur_arg], "meth") == 0) {
3904 if (!*(args[cur_arg+1])) {
3905 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3906 goto error;
3907 }
3908 cur_arg++;
3909 meth = args[cur_arg];
3910 }
3911 else if (strcmp(args[cur_arg], "uri") == 0) {
3912 if (!*(args[cur_arg+1])) {
3913 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3914 goto error;
3915 }
3916 cur_arg++;
3917 uri = args[cur_arg];
3918 // TODO: log-format uri
3919 }
Christopher Faulet907701b2020-04-28 09:37:00 +02003920 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003921 if (!*(args[cur_arg+1])) {
3922 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3923 goto error;
3924 }
3925 cur_arg++;
3926 vsn = args[cur_arg];
3927 }
3928 else if (strcmp(args[cur_arg], "hdr") == 0) {
3929 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3930 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3931 goto error;
3932 }
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003933
3934 if (strcasecmp(args[cur_arg+1], "host") == 0) {
3935 if (host_hdr >= 0) {
3936 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
3937 args[cur_arg+1], istptr(hdrs[host_hdr].v));
3938 goto error;
3939 }
3940 host_hdr = i;
3941 }
Christopher Faulet7d765db2020-04-29 11:50:01 +02003942 else if (strcasecmp(args[cur_arg+1], "connection") == 0 ||
3943 strcasecmp(args[cur_arg+1], "content-length") == 0 ||
3944 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
3945 goto skip_hdr;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003946
Christopher Faulet61cc8522020-04-20 14:54:42 +02003947 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3948 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3949 i++;
Christopher Faulet7d765db2020-04-29 11:50:01 +02003950 skip_hdr:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003951 cur_arg += 2;
3952 }
3953 else if (strcmp(args[cur_arg], "body") == 0) {
3954 if (!*(args[cur_arg+1])) {
3955 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3956 goto error;
3957 }
3958 cur_arg++;
3959 body = args[cur_arg];
3960 // TODO: log-format body
3961 }
3962 else if (strcmp(args[cur_arg], "comment") == 0) {
3963 if (!*(args[cur_arg+1])) {
3964 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3965 goto error;
3966 }
3967 cur_arg++;
3968 free(comment);
3969 comment = strdup(args[cur_arg]);
3970 if (!comment) {
3971 memprintf(errmsg, "out of memory");
3972 goto error;
3973 }
3974 }
3975 else {
Christopher Faulet907701b2020-04-28 09:37:00 +02003976 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'ver', 'hdr' and 'body' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003977 args[cur_arg]);
3978 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003979 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003980 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003981 }
3982
Christopher Faulet61cc8522020-04-20 14:54:42 +02003983 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003984
Christopher Faulet61cc8522020-04-20 14:54:42 +02003985 chk = calloc(1, sizeof(*chk));
3986 if (!chk) {
3987 memprintf(errmsg, "out of memory");
3988 goto error;
3989 }
3990 chk->action = TCPCHK_ACT_SEND;
3991 chk->comment = comment; comment = NULL;
3992 chk->send.type = TCPCHK_SEND_HTTP;
3993 chk->send.http.flags = flags;
3994 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003995
Christopher Faulet61cc8522020-04-20 14:54:42 +02003996 if (meth) {
3997 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3998 chk->send.http.meth.str.area = strdup(meth);
3999 chk->send.http.meth.str.data = strlen(meth);
4000 if (!chk->send.http.meth.str.area) {
4001 memprintf(errmsg, "out of memory");
4002 goto error;
4003 }
4004 }
4005 if (uri) {
4006 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
4007 if (!isttest(chk->send.http.uri)) {
4008 memprintf(errmsg, "out of memory");
4009 goto error;
4010 }
4011 }
4012 if (vsn) {
4013 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
4014 if (!isttest(chk->send.http.vsn)) {
4015 memprintf(errmsg, "out of memory");
4016 goto error;
4017 }
4018 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02004019 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004020 hdr = calloc(1, sizeof(*hdr));
4021 if (!hdr) {
4022 memprintf(errmsg, "out of memory");
4023 goto error;
4024 }
4025 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02004026 hdr->name = istdup(hdrs[i].n);
4027 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004028 memprintf(errmsg, "out of memory");
4029 goto error;
4030 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004031
Christopher Fauletb61caf42020-04-21 10:57:42 +02004032 ist0(hdrs[i].v);
4033 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 +02004034 goto error;
4035 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4036 hdr = NULL;
4037 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004038
Christopher Faulet61cc8522020-04-20 14:54:42 +02004039 if (body) {
4040 chk->send.http.body = ist2(strdup(body), strlen(body));
4041 if (!isttest(chk->send.http.body)) {
4042 memprintf(errmsg, "out of memory");
4043 goto error;
4044 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004045 }
4046
Christopher Faulet61cc8522020-04-20 14:54:42 +02004047 return chk;
4048
4049 error:
4050 free_tcpcheck_http_hdr(hdr);
4051 free_tcpcheck(chk, 0);
4052 free(comment);
4053 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004054}
4055
Christopher Faulet61cc8522020-04-20 14:54:42 +02004056/* Parses and creates a http-check comment rule. NULL is returned on error */
4057static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4058 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004059{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004060 struct tcpcheck_rule *chk = NULL;
4061 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004062
Christopher Faulet61cc8522020-04-20 14:54:42 +02004063 if (!*(args[cur_arg+1])) {
4064 memprintf(errmsg, "expects a string as argument");
4065 goto error;
4066 }
4067 cur_arg++;
4068 comment = strdup(args[cur_arg]);
4069 if (!comment) {
4070 memprintf(errmsg, "out of memory");
4071 goto error;
4072 }
Willy Tarreau04276f32017-01-06 17:41:29 +01004073
Christopher Faulet61cc8522020-04-20 14:54:42 +02004074 chk = calloc(1, sizeof(*chk));
4075 if (!chk) {
4076 memprintf(errmsg, "out of memory");
4077 goto error;
4078 }
4079 chk->action = TCPCHK_ACT_COMMENT;
4080 chk->comment = comment;
4081 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004082
Christopher Faulet61cc8522020-04-20 14:54:42 +02004083 error:
4084 free(comment);
4085 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004086}
4087
Christopher Faulet61cc8522020-04-20 14:54:42 +02004088/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
4089 * on error. <proto> is set to the right protocol flags (covered by the
4090 * TCPCHK_RULES_PROTO_CHK mask).
4091 */
4092static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
4093 struct list *rules, unsigned int proto,
4094 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004095{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004096 struct tcpcheck_rule *prev_check, *chk = NULL;
4097 struct sample_expr *status_expr = NULL;
Christopher Faulet39708192020-05-05 10:47:36 +02004098 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004099 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004100 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
4101 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
4102 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
Christopher Faulet39708192020-05-05 10:47:36 +02004103 unsigned int flags = 0;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004104 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02004105 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004106
Christopher Faulet39708192020-05-05 10:47:36 +02004107 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004108 if (!*(args[cur_arg+1])) {
4109 memprintf(errmsg, "expects at least a matching pattern as arguments");
4110 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004111 }
4112
Christopher Faulet61cc8522020-04-20 14:54:42 +02004113 cur_arg++;
4114 while (*(args[cur_arg])) {
4115 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004116
Christopher Faulet61cc8522020-04-20 14:54:42 +02004117 rescan:
4118 if (strcmp(args[cur_arg], "min-recv") == 0) {
4119 if (in_pattern) {
4120 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4121 goto error;
4122 }
4123 if (!*(args[cur_arg+1])) {
4124 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4125 goto error;
4126 }
4127 /* Use an signed integer here because of chksize */
4128 cur_arg++;
4129 min_recv = atol(args[cur_arg]);
4130 if (min_recv < -1 || min_recv > INT_MAX) {
4131 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4132 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02004133 }
4134 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004135 else if (*(args[cur_arg]) == '!') {
4136 in_pattern = 1;
4137 while (*(args[cur_arg]) == '!') {
4138 inverse = !inverse;
4139 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004140 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004141 if (!*(args[cur_arg]))
4142 cur_arg++;
4143 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02004144 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004145 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
4146 if (type != TCPCHK_EXPECT_UNDEF) {
4147 memprintf(errmsg, "only on pattern expected");
4148 goto error;
4149 }
4150 if (proto != TCPCHK_RULES_HTTP_CHK)
4151 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
4152 else
4153 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02004154
Christopher Faulet61cc8522020-04-20 14:54:42 +02004155 if (!*(args[cur_arg+1])) {
4156 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4157 goto error;
4158 }
4159 cur_arg++;
4160 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004161 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004162 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4163 if (proto == TCPCHK_RULES_HTTP_CHK)
4164 goto bad_http_kw;
4165 if (type != TCPCHK_EXPECT_UNDEF) {
4166 memprintf(errmsg, "only on pattern expected");
4167 goto error;
4168 }
4169 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004170
Christopher Faulet61cc8522020-04-20 14:54:42 +02004171 if (!*(args[cur_arg+1])) {
4172 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4173 goto error;
4174 }
4175 cur_arg++;
4176 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004177 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004178 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4179 if (proto != TCPCHK_RULES_HTTP_CHK)
4180 goto bad_tcp_kw;
4181 if (type != TCPCHK_EXPECT_UNDEF) {
4182 memprintf(errmsg, "only on pattern expected");
4183 goto error;
4184 }
4185 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004186
Christopher Faulet61cc8522020-04-20 14:54:42 +02004187 if (!*(args[cur_arg+1])) {
4188 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4189 goto error;
4190 }
4191 cur_arg++;
4192 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004193 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004194 else if (strcmp(args[cur_arg], "custom") == 0) {
4195 if (in_pattern) {
4196 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4197 goto error;
4198 }
4199 if (type != TCPCHK_EXPECT_UNDEF) {
4200 memprintf(errmsg, "only on pattern expected");
4201 goto error;
4202 }
4203 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004204 }
Christopher Faulet39708192020-05-05 10:47:36 +02004205 else if (strcmp(args[cur_arg], "header") == 0) {
4206 int orig_arg = cur_arg;
4207
4208 if (proto != TCPCHK_RULES_HTTP_CHK)
4209 goto bad_tcp_kw;
4210 if (type != TCPCHK_EXPECT_UNDEF) {
4211 memprintf(errmsg, "only on pattern expected");
4212 goto error;
4213 }
4214 type = TCPCHK_EXPECT_HTTP_HEADER;
4215
4216 /* Parse the name pattern, mandatory */
4217 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) || strcmp(args[cur_arg+1], "name") != 0) {
4218 memprintf(errmsg, "'%s' expects at the keyword name as first argument followed by a pattern",
4219 args[orig_arg]);
4220 goto error;
4221 }
4222 cur_arg += 2;
4223 if (strcmp(args[cur_arg], "-m") == 0) {
4224 if (!*(args[cur_arg+1])) {
4225 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
4226 args[orig_arg], args[cur_arg]);
4227 goto error;
4228 }
4229 if (strcmp(args[cur_arg+1], "str") == 0)
4230 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
4231 else if (strcmp(args[cur_arg+1], "beg") == 0)
4232 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
4233 else if (strcmp(args[cur_arg+1], "end") == 0)
4234 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
4235 else if (strcmp(args[cur_arg+1], "sub") == 0)
4236 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
4237 else if (strcmp(args[cur_arg+1], "reg") == 0)
4238 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
4239 else {
4240 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
4241 args[orig_arg], args[cur_arg], args[cur_arg+1]);
4242 goto error;
4243 }
4244 cur_arg += 2;
4245 }
4246 else
4247 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
4248 npat = args[cur_arg];
4249
4250 if (!(*args[cur_arg+1])) {
4251 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
4252 goto next;
4253 }
4254
4255 if (strcmp(args[cur_arg+1], "log-format") == 0) {
4256 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
4257 memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
4258 args[orig_arg], args[cur_arg+1]);
4259 goto error;
4260 }
4261 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
4262 cur_arg++;
4263 }
4264
4265 if (!(*args[cur_arg+1]) || strcmp(args[cur_arg+1], "value") != 0) {
4266 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
4267 goto next;
4268 }
4269
4270 /* Parse the value pattern, optionnal */
4271 cur_arg += 2;
4272 if (strcmp(args[cur_arg], "-m") == 0) {
4273 if (!*(args[cur_arg+1])) {
4274 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
4275 args[orig_arg], args[cur_arg]);
4276 goto error;
4277 }
4278 if (strcmp(args[cur_arg+1], "str") == 0)
4279 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
4280 else if (strcmp(args[cur_arg+1], "beg") == 0)
4281 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
4282 else if (strcmp(args[cur_arg+1], "end") == 0)
4283 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
4284 else if (strcmp(args[cur_arg+1], "sub") == 0)
4285 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
4286 else if (strcmp(args[cur_arg+1], "reg") == 0)
4287 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
4288 else {
4289 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
4290 args[orig_arg], args[cur_arg], args[cur_arg+1]);
4291 goto error;
4292 }
4293 cur_arg += 2;
4294 }
4295 else
4296 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
4297 vpat = args[cur_arg];
4298
4299 while (*args[cur_arg+1]) {
4300 if (strcmp(args[cur_arg+1], "log-format") == 0) {
4301 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
4302 memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
4303 args[orig_arg], args[cur_arg+1]);
4304 goto error;
4305 }
4306 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
4307 }
4308 else if (strcmp(args[cur_arg+1], "full") == 0)
4309 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
4310 else
4311 break;
4312 cur_arg++;
4313 }
4314 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004315 else if (strcmp(args[cur_arg], "comment") == 0) {
4316 if (in_pattern) {
4317 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4318 goto error;
4319 }
4320 if (!*(args[cur_arg+1])) {
4321 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4322 goto error;
4323 }
4324 cur_arg++;
4325 free(comment);
4326 comment = strdup(args[cur_arg]);
4327 if (!comment) {
4328 memprintf(errmsg, "out of memory");
4329 goto error;
4330 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004331 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004332 else if (strcmp(args[cur_arg], "on-success") == 0) {
4333 if (in_pattern) {
4334 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4335 goto error;
4336 }
4337 if (!*(args[cur_arg+1])) {
4338 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4339 goto error;
4340 }
4341 cur_arg++;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004342 on_success_msg = args[cur_arg];
Christopher Fauletd7e63962020-04-17 20:15:59 +02004343 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004344 else if (strcmp(args[cur_arg], "on-error") == 0) {
4345 if (in_pattern) {
4346 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4347 goto error;
4348 }
4349 if (!*(args[cur_arg+1])) {
4350 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4351 goto error;
4352 }
4353 cur_arg++;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004354 on_error_msg = args[cur_arg];
Christopher Faulet61cc8522020-04-20 14:54:42 +02004355 }
4356 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4357 if (in_pattern) {
4358 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4359 goto error;
4360 }
4361 if (!*(args[cur_arg+1])) {
4362 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4363 goto error;
4364 }
4365 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4366 ok_st = HCHK_STATUS_L7OKD;
4367 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4368 ok_st = HCHK_STATUS_L7OKCD;
4369 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4370 ok_st = HCHK_STATUS_L6OK;
4371 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4372 ok_st = HCHK_STATUS_L4OK;
4373 else {
4374 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4375 args[cur_arg], args[cur_arg+1]);
4376 goto error;
4377 }
4378 cur_arg++;
4379 }
4380 else if (strcmp(args[cur_arg], "error-status") == 0) {
4381 if (in_pattern) {
4382 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4383 goto error;
4384 }
4385 if (!*(args[cur_arg+1])) {
4386 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4387 goto error;
4388 }
4389 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4390 err_st = HCHK_STATUS_L7RSP;
4391 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4392 err_st = HCHK_STATUS_L7STS;
4393 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4394 err_st = HCHK_STATUS_L6RSP;
4395 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4396 err_st = HCHK_STATUS_L4CON;
4397 else {
4398 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4399 args[cur_arg], args[cur_arg+1]);
4400 goto error;
4401 }
4402 cur_arg++;
4403 }
4404 else if (strcmp(args[cur_arg], "status-code") == 0) {
4405 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004406
Christopher Faulet61cc8522020-04-20 14:54:42 +02004407 if (in_pattern) {
4408 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4409 goto error;
4410 }
4411 if (!*(args[cur_arg+1])) {
4412 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4413 goto error;
4414 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004415
Christopher Faulet61cc8522020-04-20 14:54:42 +02004416 cur_arg++;
4417 release_sample_expr(status_expr);
4418 px->conf.args.ctx = ARGC_SRV;
4419 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4420 file, line, errmsg, &px->conf.args, NULL);
4421 if (!status_expr) {
4422 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4423 goto error;
4424 }
4425 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4426 memprintf(errmsg, "error detected while parsing status-code expression : "
4427 " fetch method '%s' extracts information from '%s', "
4428 "none of which is available here.\n",
4429 args[cur_arg], sample_src_names(status_expr->fetch->use));
4430 goto error;
4431 }
4432 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4433 }
4434 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4435 if (in_pattern) {
4436 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4437 goto error;
4438 }
4439 if (!*(args[cur_arg+1])) {
4440 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4441 goto error;
4442 }
4443 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4444 tout_st = HCHK_STATUS_L7TOUT;
4445 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4446 tout_st = HCHK_STATUS_L6TOUT;
4447 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4448 tout_st = HCHK_STATUS_L4TOUT;
4449 else {
4450 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4451 args[cur_arg], args[cur_arg+1]);
4452 goto error;
4453 }
4454 cur_arg++;
4455 }
4456 else {
4457 if (proto == TCPCHK_RULES_HTTP_CHK) {
4458 bad_http_kw:
4459 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
Christopher Faulet39708192020-05-05 10:47:36 +02004460 "[!]header or comment but got '%s' as argument.", args[cur_arg]);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004461 }
4462 else {
4463 bad_tcp_kw:
4464 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4465 " or comment but got '%s' as argument.", args[cur_arg]);
4466 }
4467 goto error;
4468 }
Christopher Faulet39708192020-05-05 10:47:36 +02004469 next:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004470 cur_arg++;
4471 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004472
Christopher Faulet61cc8522020-04-20 14:54:42 +02004473 chk = calloc(1, sizeof(*chk));
4474 if (!chk) {
4475 memprintf(errmsg, "out of memory");
4476 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004477 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004478 chk->action = TCPCHK_ACT_EXPECT;
4479 LIST_INIT(&chk->expect.onerror_fmt);
4480 LIST_INIT(&chk->expect.onsuccess_fmt);
4481 chk->comment = comment; comment = NULL;
4482 chk->expect.type = type;
4483 chk->expect.min_recv = min_recv;
Christopher Faulet39708192020-05-05 10:47:36 +02004484 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004485 chk->expect.ok_status = ok_st;
4486 chk->expect.err_status = err_st;
4487 chk->expect.tout_status = tout_st;
4488 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004489
Christopher Faulet61cc8522020-04-20 14:54:42 +02004490 if (on_success_msg) {
4491 px->conf.args.ctx = ARGC_SRV;
4492 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4493 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4494 goto error;
4495 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004496 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004497 if (on_error_msg) {
4498 px->conf.args.ctx = ARGC_SRV;
4499 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4500 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4501 goto error;
4502 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004503 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004504
Christopher Faulet61cc8522020-04-20 14:54:42 +02004505 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004506 case TCPCHK_EXPECT_HTTP_STATUS: {
4507 const char *p = pattern;
4508 unsigned int c1,c2;
4509
4510 chk->expect.codes.codes = NULL;
4511 chk->expect.codes.num = 0;
4512 while (1) {
4513 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4514 if (*p == '-') {
4515 p++;
4516 c2 = read_uint(&p, pattern + strlen(pattern));
4517 }
4518 if (c1 > c2) {
4519 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4520 goto error;
4521 }
4522
4523 chk->expect.codes.num++;
4524 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4525 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4526 if (!chk->expect.codes.codes) {
4527 memprintf(errmsg, "out of memory");
4528 goto error;
4529 }
4530 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4531 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4532
4533 if (*p == '\0')
4534 break;
4535 if (*p != ',') {
4536 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4537 goto error;
4538 }
4539 p++;
4540 }
4541 break;
4542 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004543 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004544 case TCPCHK_EXPECT_HTTP_BODY:
4545 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004546 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004547 memprintf(errmsg, "out of memory");
4548 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004549 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004550 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004551 case TCPCHK_EXPECT_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02004552 int len = chk->expect.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004553
4554 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004555 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4556 goto error;
4557 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004558 chk->expect.data.len = len;
4559 break;
4560 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004561 case TCPCHK_EXPECT_REGEX:
4562 case TCPCHK_EXPECT_REGEX_BINARY:
4563 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4564 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004565 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004566 if (!chk->expect.regex)
4567 goto error;
Christopher Faulet39708192020-05-05 10:47:36 +02004568 break;
4569 case TCPCHK_EXPECT_HTTP_HEADER:
4570 if (!npat) {
4571 memprintf(errmsg, "unexpected error, undefined header name pattern");
4572 goto error;
4573 }
4574 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
4575 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
4576 if (!chk->expect.hdr.name_re)
4577 goto error;
4578 }
4579 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
4580 px->conf.args.ctx = ARGC_SRV;
4581 LIST_INIT(&chk->expect.hdr.name_fmt);
4582 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4583 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
4584 goto error;
4585 }
4586 }
4587 else {
4588 chk->expect.hdr.name = ist2(strdup(npat), strlen(npat));
4589 if (!isttest(chk->expect.hdr.name)) {
4590 memprintf(errmsg, "out of memory");
4591 goto error;
4592 }
4593 }
4594
4595 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
4596 chk->expect.hdr.value = IST_NULL;
4597 break;
4598 }
4599
4600 if (!vpat) {
4601 memprintf(errmsg, "unexpected error, undefined header value pattern");
4602 goto error;
4603 }
4604 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
4605 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
4606 if (!chk->expect.hdr.value_re)
4607 goto error;
4608 }
4609 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
4610 px->conf.args.ctx = ARGC_SRV;
4611 LIST_INIT(&chk->expect.hdr.value_fmt);
4612 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4613 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
4614 goto error;
4615 }
4616 }
4617 else {
4618 chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat));
4619 if (!isttest(chk->expect.hdr.value)) {
4620 memprintf(errmsg, "out of memory");
4621 goto error;
4622 }
4623 }
4624
Christopher Faulet61cc8522020-04-20 14:54:42 +02004625 break;
4626 case TCPCHK_EXPECT_CUSTOM:
4627 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4628 break;
4629 case TCPCHK_EXPECT_UNDEF:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004630 memprintf(errmsg, "pattern not found");
4631 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004632 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004633
Christopher Faulet61cc8522020-04-20 14:54:42 +02004634 /* All tcp-check expect points back to the first inverse expect rule in
4635 * a chain of one or more expect rule, potentially itself.
4636 */
4637 chk->expect.head = chk;
4638 list_for_each_entry_rev(prev_check, rules, list) {
4639 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4640 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4641 chk->expect.head = prev_check;
4642 continue;
4643 }
4644 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4645 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004646 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004647 return chk;
4648
4649 error:
4650 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004651 free(comment);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004652 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004653 return NULL;
4654}
4655
Christopher Faulet61cc8522020-04-20 14:54:42 +02004656/* Overwrites fields of the old http send rule with those of the new one. When
4657 * replaced, old values are freed and replaced by the new ones. New values are
4658 * not copied but transferred. At the end <new> should be empty and can be
4659 * safely released. This function never fails.
4660 */
4661static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004662{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004663 struct logformat_node *lf, *lfb;
4664 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004665
Christopher Faulet404f9192020-04-09 23:13:54 +02004666
Christopher Faulet61cc8522020-04-20 14:54:42 +02004667 if (new->send.http.meth.str.area) {
4668 free(old->send.http.meth.str.area);
4669 old->send.http.meth.meth = new->send.http.meth.meth;
4670 old->send.http.meth.str.area = new->send.http.meth.str.area;
4671 old->send.http.meth.str.data = new->send.http.meth.str.data;
4672 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004673 }
4674
Christopher Faulet61cc8522020-04-20 14:54:42 +02004675 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4676 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004677 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004678 else
4679 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4680 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4681 old->send.http.uri = new->send.http.uri;
4682 new->send.http.uri = IST_NULL;
4683 }
4684 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4685 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004686 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004687 else
4688 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4689 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4690 LIST_INIT(&old->send.http.uri_fmt);
4691 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4692 LIST_DEL(&lf->list);
4693 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4694 }
4695 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004696
Christopher Faulet61cc8522020-04-20 14:54:42 +02004697 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004698 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004699 old->send.http.vsn = new->send.http.vsn;
4700 new->send.http.vsn = IST_NULL;
4701 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004702
Christopher Faulet61cc8522020-04-20 14:54:42 +02004703 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4704 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4705 LIST_DEL(&hdr->list);
4706 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004707 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708
4709 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4710 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004711 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004712 else
4713 free_tcpcheck_fmt(&old->send.http.body_fmt);
4714 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4715 old->send.http.body = new->send.http.body;
4716 new->send.http.body = IST_NULL;
4717 }
4718 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4719 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004720 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004721 else
4722 free_tcpcheck_fmt(&old->send.http.body_fmt);
4723 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4724 LIST_INIT(&old->send.http.body_fmt);
4725 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4726 LIST_DEL(&lf->list);
4727 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4728 }
4729 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004730}
4731
Christopher Faulet61cc8522020-04-20 14:54:42 +02004732/* Internal function used to add an http-check rule in a list during the config
4733 * parsing step. Depending on its type, and the previously inserted rules, a
4734 * specific action may be performed or an error may be reported. This functions
4735 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4736 * message.
4737 */
4738static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004739{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004740 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004741
Christopher Faulet61cc8522020-04-20 14:54:42 +02004742 /* the implicit send rule coming from an "option httpchk" line must be
4743 * merged with the first explici http-check send rule, if
4744 * any. Depdending the declaration order some tests are required.
4745 *
4746 * Some tests is also required for other kinds of http-check rules to be
4747 * sure the ruleset remains valid.
4748 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004749
Christopher Faulet61cc8522020-04-20 14:54:42 +02004750 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4751 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4752 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4753 * following tests are performed :
4754 *
4755 * 1- If there is no such rule or if it is not a send rule, the implicit send
4756 * rule is pushed in front of the ruleset
4757 *
4758 * 2- If it is another implicit send rule, it is replaced with the new one.
4759 *
4760 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4761 * both, overwritting the old send rule (the explicit one) with info of the
4762 * new send rule (the implicit one).
4763 */
4764 r = get_first_tcpcheck_rule(rules);
4765 if (r && r->action == TCPCHK_ACT_CONNECT)
4766 r = get_next_tcpcheck_rule(rules, r);
4767 if (!r || r->action != TCPCHK_ACT_SEND)
4768 LIST_ADD(rules->list, &chk->list);
4769 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4770 LIST_DEL(&r->list);
4771 free_tcpcheck(r, 0);
4772 LIST_ADD(rules->list, &chk->list);
4773 }
4774 else {
4775 tcpcheck_overwrite_send_http_rule(r, chk);
4776 free_tcpcheck(chk, 0);
4777 }
4778 }
4779 else {
4780 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4781 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4782 * with an existing implicit send rule, if any. At the end, if there is no error,
4783 * the rule is appended to the list.
4784 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004785
Christopher Faulet61cc8522020-04-20 14:54:42 +02004786 r = get_last_tcpcheck_rule(rules);
4787 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4788 /* no error */;
4789 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4790 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4791 chk->index+1);
4792 return 0;
4793 }
Christopher Faulet1a200d62020-05-05 07:59:23 +02004794 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004795 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4796 chk->index+1);
4797 return 0;
4798 }
4799 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4800 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4801 chk->index+1);
4802 return 0;
4803 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004804
Christopher Faulet61cc8522020-04-20 14:54:42 +02004805 if (chk->action == TCPCHK_ACT_SEND) {
4806 r = get_first_tcpcheck_rule(rules);
4807 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4808 tcpcheck_overwrite_send_http_rule(r, chk);
4809 free_tcpcheck(chk, 0);
4810 LIST_DEL(&r->list);
4811 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4812 chk = r;
4813 }
4814 }
4815 LIST_ADDQ(rules->list, &chk->list);
4816 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004817 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004818}
4819
Christopher Faulet61cc8522020-04-20 14:54:42 +02004820/**************************************************************************/
4821/************************** Init/deinit checks ****************************/
4822/**************************************************************************/
4823static const char *init_check(struct check *check, int type)
4824{
4825 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004826
Christopher Faulet61cc8522020-04-20 14:54:42 +02004827 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4828 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004829
Christopher Faulet61cc8522020-04-20 14:54:42 +02004830 check->bi.area = calloc(check->bi.size, sizeof(char));
4831 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004832
Christopher Faulet61cc8522020-04-20 14:54:42 +02004833 if (!check->bi.area || !check->bo.area)
4834 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004835
Christopher Faulet61cc8522020-04-20 14:54:42 +02004836 check->wait_list.tasklet = tasklet_new();
4837 if (!check->wait_list.tasklet)
4838 return "out of memory while allocating check tasklet";
4839 check->wait_list.events = 0;
4840 check->wait_list.tasklet->process = event_srv_chk_io;
4841 check->wait_list.tasklet->context = check;
4842 return NULL;
4843}
4844
4845void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004846{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004847 task_destroy(check->task);
4848 if (check->wait_list.tasklet)
4849 tasklet_free(check->wait_list.tasklet);
4850
4851 free(check->bi.area);
4852 free(check->bo.area);
4853 if (check->cs) {
4854 free(check->cs->conn);
4855 check->cs->conn = NULL;
4856 cs_free(check->cs);
4857 check->cs = NULL;
4858 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004859}
4860
Christopher Faulet61cc8522020-04-20 14:54:42 +02004861/* manages a server health-check. Returns the time the task accepts to wait, or
4862 * TIME_ETERNITY for infinity.
4863 */
4864static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004865{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004866 struct check *check = context;
4867
4868 if (check->type == PR_O2_EXT_CHK)
4869 return process_chk_proc(t, context, state);
4870 return process_chk_conn(t, context, state);
4871
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004872}
4873
Christopher Faulet61cc8522020-04-20 14:54:42 +02004874
4875static int start_check_task(struct check *check, int mininter,
4876 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004877{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004878 struct task *t;
4879 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004880
Christopher Faulet61cc8522020-04-20 14:54:42 +02004881 if (check->type == PR_O2_EXT_CHK)
4882 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004883
Christopher Faulet61cc8522020-04-20 14:54:42 +02004884 /* task for the check */
4885 if ((t = task_new(thread_mask)) == NULL) {
4886 ha_alert("Starting [%s:%s] check: out of memory.\n",
4887 check->server->proxy->id, check->server->id);
4888 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004889 }
4890
Christopher Faulet61cc8522020-04-20 14:54:42 +02004891 check->task = t;
4892 t->process = process_chk;
4893 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004894
Christopher Faulet61cc8522020-04-20 14:54:42 +02004895 if (mininter < srv_getinter(check))
4896 mininter = srv_getinter(check);
4897
4898 if (global.max_spread_checks && mininter > global.max_spread_checks)
4899 mininter = global.max_spread_checks;
4900
4901 /* check this every ms */
4902 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4903 check->start = now;
4904 task_queue(t);
4905
4906 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004907}
4908
Christopher Faulet61cc8522020-04-20 14:54:42 +02004909/* updates the server's weight during a warmup stage. Once the final weight is
4910 * reached, the task automatically stops. Note that any server status change
4911 * must have updated s->last_change accordingly.
4912 */
4913static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004914{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004915 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004916
Christopher Faulet61cc8522020-04-20 14:54:42 +02004917 /* by default, plan on stopping the task */
4918 t->expire = TICK_ETERNITY;
4919 if ((s->next_admin & SRV_ADMF_MAINT) ||
4920 (s->next_state != SRV_ST_STARTING))
4921 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004922
Christopher Faulet61cc8522020-04-20 14:54:42 +02004923 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004924
Christopher Faulet61cc8522020-04-20 14:54:42 +02004925 /* recalculate the weights and update the state */
4926 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004927
Christopher Faulet61cc8522020-04-20 14:54:42 +02004928 /* probably that we can refill this server with a bit more connections */
4929 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004930
Christopher Faulet61cc8522020-04-20 14:54:42 +02004931 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004932
Christopher Faulet61cc8522020-04-20 14:54:42 +02004933 /* get back there in 1 second or 1/20th of the slowstart interval,
4934 * whichever is greater, resulting in small 5% steps.
4935 */
4936 if (s->next_state == SRV_ST_STARTING)
4937 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4938 return t;
4939}
4940
4941/*
4942 * Start health-check.
4943 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4944 */
4945static int start_checks()
4946{
4947
4948 struct proxy *px;
4949 struct server *s;
4950 struct task *t;
4951 int nbcheck=0, mininter=0, srvpos=0;
4952
4953 /* 0- init the dummy frontend used to create all checks sessions */
4954 init_new_proxy(&checks_fe);
4955 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4956 checks_fe.mode = PR_MODE_TCP;
4957 checks_fe.maxconn = 0;
4958 checks_fe.conn_retries = CONN_RETRIES;
4959 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4960 checks_fe.timeout.client = TICK_ETERNITY;
4961
4962 /* 1- count the checkers to run simultaneously.
4963 * We also determine the minimum interval among all of those which
4964 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4965 * will be used to spread their start-up date. Those which have
4966 * a shorter interval will start independently and will not dictate
4967 * too short an interval for all others.
4968 */
4969 for (px = proxies_list; px; px = px->next) {
4970 for (s = px->srv; s; s = s->next) {
4971 if (s->slowstart) {
4972 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4973 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4974 return ERR_ALERT | ERR_FATAL;
4975 }
4976 /* We need a warmup task that will be called when the server
4977 * state switches from down to up.
4978 */
4979 s->warmup = t;
4980 t->process = server_warmup;
4981 t->context = s;
4982 /* server can be in this state only because of */
4983 if (s->next_state == SRV_ST_STARTING)
4984 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 +02004985 }
4986
Christopher Faulet61cc8522020-04-20 14:54:42 +02004987 if (s->check.state & CHK_ST_CONFIGURED) {
4988 nbcheck++;
4989 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4990 (!mininter || mininter > srv_getinter(&s->check)))
4991 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004992 }
4993
Christopher Faulet61cc8522020-04-20 14:54:42 +02004994 if (s->agent.state & CHK_ST_CONFIGURED) {
4995 nbcheck++;
4996 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4997 (!mininter || mininter > srv_getinter(&s->agent)))
4998 mininter = srv_getinter(&s->agent);
4999 }
Christopher Faulet5c288742020-03-31 08:15:58 +02005000 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005001 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02005002
Christopher Faulet61cc8522020-04-20 14:54:42 +02005003 if (!nbcheck)
5004 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02005005
Christopher Faulet61cc8522020-04-20 14:54:42 +02005006 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02005007
Christopher Faulet61cc8522020-04-20 14:54:42 +02005008 /*
5009 * 2- start them as far as possible from each others. For this, we will
5010 * start them after their interval set to the min interval divided by
5011 * the number of servers, weighted by the server's position in the list.
5012 */
5013 for (px = proxies_list; px; px = px->next) {
5014 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
5015 if (init_pid_list()) {
5016 ha_alert("Starting [%s] check: out of memory.\n", px->id);
5017 return ERR_ALERT | ERR_FATAL;
5018 }
5019 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02005020
Christopher Faulet61cc8522020-04-20 14:54:42 +02005021 for (s = px->srv; s; s = s->next) {
5022 /* A task for the main check */
5023 if (s->check.state & CHK_ST_CONFIGURED) {
5024 if (s->check.type == PR_O2_EXT_CHK) {
5025 if (!prepare_external_check(&s->check))
5026 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02005027 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005028 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
5029 return ERR_ALERT | ERR_FATAL;
5030 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02005031 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005032
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033 /* A task for a auxiliary agent check */
5034 if (s->agent.state & CHK_ST_CONFIGURED) {
5035 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
5036 return ERR_ALERT | ERR_FATAL;
5037 }
5038 srvpos++;
5039 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005040 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005041 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005042 return 0;
5043}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005044
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005045
Christopher Faulet61cc8522020-04-20 14:54:42 +02005046/*
5047 * Return value:
5048 * the port to be used for the health check
5049 * 0 in case no port could be found for the check
5050 */
5051static int srv_check_healthcheck_port(struct check *chk)
5052{
5053 int i = 0;
5054 struct server *srv = NULL;
5055
5056 srv = chk->server;
5057
5058 /* by default, we use the health check port ocnfigured */
5059 if (chk->port > 0)
5060 return chk->port;
5061
5062 /* try to get the port from check_core.addr if check.port not set */
5063 i = get_host_port(&chk->addr);
5064 if (i > 0)
5065 return i;
5066
5067 /* try to get the port from server address */
5068 /* prevent MAPPORTS from working at this point, since checks could
5069 * not be performed in such case (MAPPORTS impose a relative ports
5070 * based on live traffic)
5071 */
5072 if (srv->flags & SRV_F_MAPPORTS)
5073 return 0;
5074
5075 i = srv->svc_port; /* by default */
5076 if (i > 0)
5077 return i;
5078
5079 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005080}
5081
Christopher Faulet61cc8522020-04-20 14:54:42 +02005082/* Initializes an health-check attached to the server <srv>. Non-zero is returned
5083 * if an error occurred.
5084 */
5085static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005086{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005087 const char *err;
5088 struct tcpcheck_rule *r;
5089 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005090
Christopher Faulet61cc8522020-04-20 14:54:42 +02005091 if (!srv->do_check)
5092 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005093
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005094
Christopher Faulet61cc8522020-04-20 14:54:42 +02005095 /* If neither a port nor an addr was specified and no check transport
5096 * layer is forced, then the transport layer used by the checks is the
5097 * same as for the production traffic. Otherwise we use raw_sock by
5098 * default, unless one is specified.
5099 */
5100 if (!srv->check.port && !is_addr(&srv->check.addr)) {
5101 if (!srv->check.use_ssl && srv->use_ssl != -1) {
5102 srv->check.use_ssl = srv->use_ssl;
5103 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005104 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005105 else if (srv->check.use_ssl == 1)
5106 srv->check.xprt = xprt_get(XPRT_SSL);
5107 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005108 }
5109
Christopher Faulet12882cf2020-04-23 15:50:18 +02005110 /* Inherit the mux protocol from the server if not already defined for
5111 * the check
5112 */
5113 if (srv->mux_proto && !srv->check.mux_proto)
5114 srv->check.mux_proto = srv->mux_proto;
5115
Christopher Faulet61cc8522020-04-20 14:54:42 +02005116 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005117
Christopher Faulet61cc8522020-04-20 14:54:42 +02005118 /* We need at least a service port, a check port or the first tcp-check
5119 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
5120 */
5121 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
5122 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
5123 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005124
Christopher Faulet61cc8522020-04-20 14:54:42 +02005125 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
5126 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
5127 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5128 ret |= ERR_ALERT | ERR_ABORT;
5129 goto out;
5130 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005131
Christopher Faulet61cc8522020-04-20 14:54:42 +02005132 /* search the first action (connect / send / expect) in the list */
5133 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
5134 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
5135 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
5136 "nor tcp_check rule 'connect' with port information.\n",
5137 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5138 ret |= ERR_ALERT | ERR_ABORT;
5139 goto out;
5140 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005141
Christopher Faulet61cc8522020-04-20 14:54:42 +02005142 /* scan the tcp-check ruleset to ensure a port has been configured */
5143 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
5144 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
5145 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
5146 "and a tcp_check rule 'connect' with no port information.\n",
5147 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5148 ret |= ERR_ALERT | ERR_ABORT;
5149 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005150 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005151 }
5152
Christopher Faulet61cc8522020-04-20 14:54:42 +02005153 init:
5154 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
5155 struct tcpcheck_ruleset *rs = NULL;
5156 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
5157 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005158
Christopher Faulet61cc8522020-04-20 14:54:42 +02005159 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
5160 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02005161
Christopher Faulet61cc8522020-04-20 14:54:42 +02005162 rs = find_tcpcheck_ruleset("*tcp-check");
5163 if (!rs) {
5164 rs = create_tcpcheck_ruleset("*tcp-check");
5165 if (rs == NULL) {
5166 ha_alert("config: %s '%s': out of memory.\n",
5167 proxy_type_str(srv->proxy), srv->proxy->id);
5168 ret |= ERR_ALERT | ERR_FATAL;
5169 goto out;
5170 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005171 }
5172
Christopher Faulet61cc8522020-04-20 14:54:42 +02005173 free_tcpcheck_vars(&rules->preset_vars);
5174 rules->list = &rs->rules;
5175 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005176 }
5177
Christopher Faulet61cc8522020-04-20 14:54:42 +02005178 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
5179 if (err) {
5180 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
5181 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
5182 ret |= ERR_ALERT | ERR_ABORT;
5183 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005184 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005185 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
5186 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02005187
Christopher Faulet61cc8522020-04-20 14:54:42 +02005188 out:
5189 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005190}
5191
Christopher Faulet61cc8522020-04-20 14:54:42 +02005192/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
5193 * if an error occurred.
5194 */
5195static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02005196{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005197 struct tcpcheck_rule *chk;
5198 const char *err;
5199 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005200
Christopher Faulet61cc8522020-04-20 14:54:42 +02005201 if (!srv->do_agent)
5202 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005203
Christopher Faulet61cc8522020-04-20 14:54:42 +02005204 /* If there is no connect rule preceeding all send / expect rules, an
5205 * implicit one is inserted before all others.
5206 */
5207 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
5208 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
5209 chk = calloc(1, sizeof(*chk));
5210 if (!chk) {
5211 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
5212 " to agent-check for server '%s' (out of memory).\n",
5213 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5214 ret |= ERR_ALERT | ERR_FATAL;
5215 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005216 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005217 chk->action = TCPCHK_ACT_CONNECT;
5218 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
5219 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02005220 }
5221
Christopher Faulete5870d82020-04-15 11:32:03 +02005222
Christopher Faulet61cc8522020-04-20 14:54:42 +02005223 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
5224 if (err) {
5225 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
5226 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
5227 ret |= ERR_ALERT | ERR_ABORT;
5228 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005229 }
5230
Christopher Faulet61cc8522020-04-20 14:54:42 +02005231 if (!srv->agent.inter)
5232 srv->agent.inter = srv->check.inter;
5233
5234 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
5235 global.maxsock++;
5236
5237 out:
5238 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005239}
5240
Christopher Faulet61cc8522020-04-20 14:54:42 +02005241/* Check tcp-check health-check configuration for the proxy <px>. */
5242static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005243{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005244 struct tcpcheck_rule *chk, *back;
5245 char *comment = NULL, *errmsg = NULL;
5246 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
5247 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005248
Christopher Faulet61cc8522020-04-20 14:54:42 +02005249 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
5250 deinit_proxy_tcpcheck(px);
5251 goto out;
5252 }
5253
5254 free(px->check_command);
5255 free(px->check_path);
5256 px->check_command = px->check_path = NULL;
5257
5258 if (!px->tcpcheck_rules.list) {
5259 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
5260 ret |= ERR_ALERT | ERR_FATAL;
5261 goto out;
5262 }
5263
5264 /* HTTP ruleset only : */
5265 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5266 struct tcpcheck_rule *next;
5267
5268 /* move remaining implicit send rule from "option httpchk" line to the right place.
5269 * If such rule exists, it must be the first one. In this case, the rule is moved
5270 * after the first connect rule, if any. Otherwise, nothing is done.
5271 */
5272 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
5273 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5274 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
5275 if (next && next->action == TCPCHK_ACT_CONNECT) {
5276 LIST_DEL(&chk->list);
5277 LIST_ADD(&next->list, &chk->list);
5278 chk->index = next->index;
5279 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005280 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005281
5282 /* add implicit expect rule if the last one is a send. It is inherited from previous
5283 * versions where the http expect rule was optional. Now it is possible to chained
5284 * send/expect rules but the last expect may still be implicit.
5285 */
5286 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
5287 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02005288 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02005289 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
5290 px->conf.file, px->conf.line, &errmsg);
5291 if (!next) {
5292 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
5293 "(%s).\n", px->id, errmsg);
5294 free(errmsg);
5295 ret |= ERR_ALERT | ERR_FATAL;
5296 goto out;
5297 }
5298 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
5299 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02005300 }
5301 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005302
5303 /* For all ruleset: */
5304
5305 /* If there is no connect rule preceeding all send / expect rules, an
5306 * implicit one is inserted before all others.
5307 */
5308 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
5309 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
5310 chk = calloc(1, sizeof(*chk));
5311 if (!chk) {
5312 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
5313 "(out of memory).\n", px->id);
5314 ret |= ERR_ALERT | ERR_FATAL;
5315 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005316 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005317 chk->action = TCPCHK_ACT_CONNECT;
5318 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
5319 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
5320 }
5321
5322 /* Remove all comment rules. To do so, when a such rule is found, the
5323 * comment is assigned to the following rule(s).
5324 */
5325 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5326 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5327 free(comment);
5328 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005329 }
5330
Christopher Faulet61cc8522020-04-20 14:54:42 +02005331 prev_action = chk->action;
5332 switch (chk->action) {
5333 case TCPCHK_ACT_COMMENT:
5334 free(comment);
5335 comment = chk->comment;
5336 LIST_DEL(&chk->list);
5337 free(chk);
5338 break;
5339 case TCPCHK_ACT_CONNECT:
5340 if (!chk->comment && comment)
5341 chk->comment = strdup(comment);
5342 /* fall though */
5343 case TCPCHK_ACT_ACTION_KW:
5344 free(comment);
5345 comment = NULL;
5346 break;
5347 case TCPCHK_ACT_SEND:
5348 case TCPCHK_ACT_EXPECT:
5349 if (!chk->comment && comment)
5350 chk->comment = strdup(comment);
5351 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005352 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005353 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005354 free(comment);
5355 comment = NULL;
5356
5357 out:
5358 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005359}
5360
Christopher Faulet61cc8522020-04-20 14:54:42 +02005361void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005362{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005363 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5364 px->tcpcheck_rules.flags = 0;
5365 px->tcpcheck_rules.list = NULL;
5366}
Christopher Faulete5870d82020-04-15 11:32:03 +02005367
Christopher Faulet61cc8522020-04-20 14:54:42 +02005368static void deinit_srv_check(struct server *srv)
5369{
5370 if (srv->check.state & CHK_ST_CONFIGURED)
5371 free_check(&srv->check);
5372 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5373 srv->do_check = 0;
5374}
Christopher Faulete5870d82020-04-15 11:32:03 +02005375
Christopher Faulet61cc8522020-04-20 14:54:42 +02005376
5377static void deinit_srv_agent_check(struct server *srv)
5378{
5379 if (srv->agent.tcpcheck_rules) {
5380 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5381 free(srv->agent.tcpcheck_rules);
5382 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005383 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005384
Christopher Faulet61cc8522020-04-20 14:54:42 +02005385 if (srv->agent.state & CHK_ST_CONFIGURED)
5386 free_check(&srv->agent);
5387
5388 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5389 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005390}
5391
Christopher Faulet61cc8522020-04-20 14:54:42 +02005392static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005393{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005394 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005395 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005396 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005397
Christopher Fauletd7cee712020-04-21 13:45:00 +02005398 node = ebpt_first(&shared_tcpchecks);
5399 while (node) {
5400 next = ebpt_next(node);
5401 ebpt_delete(node);
5402 free(node->key);
5403 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005404 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5405 LIST_DEL(&r->list);
5406 free_tcpcheck(r, 0);
5407 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005408 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005409 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005410 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005411}
Christopher Faulete5870d82020-04-15 11:32:03 +02005412
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005413
Christopher Faulet61cc8522020-04-20 14:54:42 +02005414REGISTER_POST_SERVER_CHECK(init_srv_check);
5415REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5416REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5417REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005418
Christopher Faulet61cc8522020-04-20 14:54:42 +02005419REGISTER_SERVER_DEINIT(deinit_srv_check);
5420REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5421REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5422REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005423
Christopher Faulet61cc8522020-04-20 14:54:42 +02005424/**************************************************************************/
5425/****************************** Email alerts ******************************/
5426/* NOTE: It may be pertinent to use an applet to handle email alerts */
5427/* instead of a tcp-check ruleset */
5428/**************************************************************************/
5429void email_alert_free(struct email_alert *alert)
5430{
5431 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005432
Christopher Faulet61cc8522020-04-20 14:54:42 +02005433 if (!alert)
5434 return;
5435
5436 if (alert->rules.list) {
5437 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5438 LIST_DEL(&rule->list);
5439 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005440 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005441 free_tcpcheck_vars(&alert->rules.preset_vars);
5442 free(alert->rules.list);
5443 alert->rules.list = NULL;
5444 }
5445 pool_free(pool_head_email_alert, alert);
5446}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005447
Christopher Faulet61cc8522020-04-20 14:54:42 +02005448static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5449{
5450 struct check *check = context;
5451 struct email_alertq *q;
5452 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005453
Christopher Faulet61cc8522020-04-20 14:54:42 +02005454 q = container_of(check, typeof(*q), check);
5455
5456 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5457 while (1) {
5458 if (!(check->state & CHK_ST_ENABLED)) {
5459 if (LIST_ISEMPTY(&q->email_alerts)) {
5460 /* All alerts processed, queue the task */
5461 t->expire = TICK_ETERNITY;
5462 task_queue(t);
5463 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005464 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005465
5466 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5467 LIST_DEL(&alert->list);
5468 t->expire = now_ms;
5469 check->tcpcheck_rules = &alert->rules;
5470 check->status = HCHK_STATUS_INI;
5471 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005472 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005473
5474 process_chk(t, context, state);
5475 if (check->state & CHK_ST_INPROGRESS)
5476 break;
5477
5478 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5479 email_alert_free(alert);
5480 check->tcpcheck_rules = NULL;
5481 check->server = NULL;
5482 check->state &= ~CHK_ST_ENABLED;
5483 }
5484 end:
5485 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5486 return t;
5487}
5488
5489/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5490 *
5491 * The function returns 1 in success case, otherwise, it returns 0 and err is
5492 * filled.
5493 */
5494int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5495{
5496 struct mailer *mailer;
5497 struct email_alertq *queues;
5498 const char *err_str;
5499 int i = 0;
5500
5501 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5502 memprintf(err, "out of memory while allocating mailer alerts queues");
5503 goto fail_no_queue;
5504 }
5505
5506 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5507 struct email_alertq *q = &queues[i];
5508 struct check *check = &q->check;
5509 struct task *t;
5510
5511 LIST_INIT(&q->email_alerts);
5512 HA_SPIN_INIT(&q->lock);
5513 check->inter = mls->timeout.mail;
5514 check->rise = DEF_AGENT_RISETIME;
5515 check->proxy = p;
5516 check->fall = DEF_AGENT_FALLTIME;
5517 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5518 memprintf(err, "%s", err_str);
5519 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005520 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005521
5522 check->xprt = mailer->xprt;
5523 check->addr = mailer->addr;
5524 check->port = get_host_port(&mailer->addr);
5525
5526 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5527 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005528 goto error;
5529 }
5530
Christopher Faulet61cc8522020-04-20 14:54:42 +02005531 check->task = t;
5532 t->process = process_email_alert;
5533 t->context = check;
5534
5535 /* check this in one ms */
5536 t->expire = TICK_ETERNITY;
5537 check->start = now;
5538 task_queue(t);
5539 }
5540
5541 mls->users++;
5542 free(p->email_alert.mailers.name);
5543 p->email_alert.mailers.m = mls;
5544 p->email_alert.queues = queues;
5545 return 0;
5546
5547 error:
5548 for (i = 0; i < mls->count; i++) {
5549 struct email_alertq *q = &queues[i];
5550 struct check *check = &q->check;
5551
5552 free_check(check);
5553 }
5554 free(queues);
5555 fail_no_queue:
5556 return 1;
5557}
5558
5559static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5560{
5561 struct tcpcheck_rule *tcpcheck, *prev_check;
5562 struct tcpcheck_expect *expect;
5563
5564 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5565 return 0;
5566 memset(tcpcheck, 0, sizeof(*tcpcheck));
5567 tcpcheck->action = TCPCHK_ACT_EXPECT;
5568
5569 expect = &tcpcheck->expect;
5570 expect->type = TCPCHK_EXPECT_STRING;
5571 LIST_INIT(&expect->onerror_fmt);
5572 LIST_INIT(&expect->onsuccess_fmt);
5573 expect->ok_status = HCHK_STATUS_L7OKD;
5574 expect->err_status = HCHK_STATUS_L7RSP;
5575 expect->tout_status = HCHK_STATUS_L7TOUT;
5576 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005577 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005578 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5579 return 0;
5580 }
5581
5582 /* All tcp-check expect points back to the first inverse expect rule
5583 * in a chain of one or more expect rule, potentially itself.
5584 */
5585 tcpcheck->expect.head = tcpcheck;
5586 list_for_each_entry_rev(prev_check, rules->list, list) {
5587 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5588 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5589 tcpcheck->expect.head = prev_check;
5590 continue;
5591 }
5592 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5593 break;
5594 }
5595 LIST_ADDQ(rules->list, &tcpcheck->list);
5596 return 1;
5597}
5598
5599static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5600{
5601 struct tcpcheck_rule *tcpcheck;
5602 struct tcpcheck_send *send;
5603 const char *in;
5604 char *dst;
5605 int i;
5606
5607 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5608 return 0;
5609 memset(tcpcheck, 0, sizeof(*tcpcheck));
5610 tcpcheck->action = TCPCHK_ACT_SEND;
5611
5612 send = &tcpcheck->send;
5613 send->type = TCPCHK_SEND_STRING;
5614
5615 for (i = 0; strs[i]; i++)
5616 send->data.len += strlen(strs[i]);
5617
Christopher Fauletb61caf42020-04-21 10:57:42 +02005618 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005619 if (!isttest(send->data)) {
5620 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5621 return 0;
5622 }
5623
Christopher Fauletb61caf42020-04-21 10:57:42 +02005624 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005625 for (i = 0; strs[i]; i++)
5626 for (in = strs[i]; (*dst = *in++); dst++);
5627 *dst = 0;
5628
5629 LIST_ADDQ(rules->list, &tcpcheck->list);
5630 return 1;
5631}
5632
5633static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5634 struct email_alertq *q, const char *msg)
5635{
5636 struct email_alert *alert;
5637 struct tcpcheck_rule *tcpcheck;
5638 struct check *check = &q->check;
5639
5640 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5641 goto error;
5642 LIST_INIT(&alert->list);
5643 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5644 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5645 if (!alert->rules.list)
5646 goto error;
5647 LIST_INIT(alert->rules.list);
5648 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5649 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005650
Christopher Faulet61cc8522020-04-20 14:54:42 +02005651 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5652 goto error;
5653 memset(tcpcheck, 0, sizeof(*tcpcheck));
5654 tcpcheck->action = TCPCHK_ACT_CONNECT;
5655 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005656
Christopher Faulet61cc8522020-04-20 14:54:42 +02005657 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005658
Christopher Faulet61cc8522020-04-20 14:54:42 +02005659 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005660 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005661
5662 {
5663 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5664 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5665 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005666 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005667
Christopher Faulet61cc8522020-04-20 14:54:42 +02005668 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5669 goto error;
5670
5671 {
5672 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5673 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005674 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005675 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005676
5677 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5678 goto error;
5679
5680 {
5681 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5682 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005683 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005684 }
5685
Christopher Faulet61cc8522020-04-20 14:54:42 +02005686 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5687 goto error;
5688
5689 {
5690 const char * const strs[2] = { "DATA\r\n" };
5691 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005692 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005693 }
5694
5695 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5696 goto error;
5697
5698 {
5699 struct tm tm;
5700 char datestr[48];
5701 const char * const strs[18] = {
5702 "From: ", p->email_alert.from, "\r\n",
5703 "To: ", p->email_alert.to, "\r\n",
5704 "Date: ", datestr, "\r\n",
5705 "Subject: [HAproxy Alert] ", msg, "\r\n",
5706 "\r\n",
5707 msg, "\r\n",
5708 "\r\n",
5709 ".\r\n",
5710 NULL
5711 };
5712
5713 get_localtime(date.tv_sec, &tm);
5714
5715 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005716 goto error;
5717 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005718
5719 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005720 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005721 }
5722
5723 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005724 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005725
5726 {
5727 const char * const strs[2] = { "QUIT\r\n" };
5728 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5729 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005730 }
5731
Christopher Faulet61cc8522020-04-20 14:54:42 +02005732 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5733 goto error;
5734
5735 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5736 task_wakeup(check->task, TASK_WOKEN_MSG);
5737 LIST_ADDQ(&q->email_alerts, &alert->list);
5738 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5739 return 1;
5740
5741error:
5742 email_alert_free(alert);
5743 return 0;
5744}
5745
5746static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5747{
5748 int i;
5749 struct mailer *mailer;
5750
5751 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5752 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5753 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5754 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5755 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005756 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005757 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005758
Christopher Faulet61cc8522020-04-20 14:54:42 +02005759 return;
5760}
5761
5762/*
5763 * Send email alert if configured.
5764 */
5765void send_email_alert(struct server *s, int level, const char *format, ...)
5766{
5767 va_list argp;
5768 char buf[1024];
5769 int len;
5770 struct proxy *p = s->proxy;
5771
5772 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5773 return;
5774
5775 va_start(argp, format);
5776 len = vsnprintf(buf, sizeof(buf), format, argp);
5777 va_end(argp);
5778
5779 if (len < 0 || len >= sizeof(buf)) {
5780 ha_alert("Email alert [%s] could not format message\n", p->id);
5781 return;
5782 }
5783
5784 enqueue_email_alert(p, s, buf);
5785}
5786
5787/**************************************************************************/
5788/************************** Check sample fetches **************************/
5789/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005790
Christopher Faulet61cc8522020-04-20 14:54:42 +02005791static struct sample_fetch_kw_list smp_kws = {ILH, {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005792 { /* END */ },
5793}};
5794
5795INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5796
5797
5798/**************************************************************************/
5799/************************ Check's parsing functions ***********************/
5800/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005801/* Parses the "tcp-check" proxy keyword */
5802static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5803 struct proxy *defpx, const char *file, int line,
5804 char **errmsg)
5805{
Christopher Faulet404f9192020-04-09 23:13:54 +02005806 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005807 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005808 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005809
5810 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5811 ret = 1;
5812
Christopher Faulet404f9192020-04-09 23:13:54 +02005813 /* Deduce the ruleset name from the proxy info */
5814 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5815 ((curpx == defpx) ? "defaults" : curpx->id),
5816 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005817
Christopher Faulet61cc8522020-04-20 14:54:42 +02005818 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005819 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005820 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005821 if (rs == NULL) {
5822 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005823 goto error;
5824 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005825 }
5826
Gaetan Rivet5301b012020-02-25 17:19:17 +01005827 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005828 if (!LIST_ISEMPTY(&rs->rules)) {
5829 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005830 index = chk->index + 1;
5831 }
5832
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005833 cur_arg = 1;
5834 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005835 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005836 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005837 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005838 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005839 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005840 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005841 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005842 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005843 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5844
5845 if (!kw) {
5846 action_kw_tcp_check_build_list(&trash);
5847 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5848 "%s%s. but got '%s'",
5849 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5850 goto error;
5851 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005852 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005853 }
5854
5855 if (!chk) {
5856 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5857 goto error;
5858 }
Christopher Faulet528f4812020-04-28 10:47:28 +02005859 ret = (ret || (*errmsg != NULL)); /* Handle warning */
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005860
5861 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005862 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005863 LIST_ADDQ(&rs->rules, &chk->list);
5864
5865 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005866 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005867 /* Use this ruleset if the proxy already has tcp-check enabled */
5868 curpx->tcpcheck_rules.list = &rs->rules;
5869 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5870 }
5871 else {
5872 /* mark this ruleset as unused for now */
5873 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5874 }
5875
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005876 return ret;
5877
5878 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005879 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005880 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005881 return -1;
5882}
5883
Christopher Faulet51b129f2020-04-09 15:54:18 +02005884/* Parses the "http-check" proxy keyword */
5885static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5886 struct proxy *defpx, const char *file, int line,
5887 char **errmsg)
5888{
Christopher Faulete5870d82020-04-15 11:32:03 +02005889 struct tcpcheck_ruleset *rs = NULL;
5890 struct tcpcheck_rule *chk = NULL;
5891 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005892
5893 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5894 ret = 1;
5895
5896 cur_arg = 1;
5897 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5898 /* enable a graceful server shutdown on an HTTP 404 response */
5899 curpx->options |= PR_O_DISABLE404;
5900 if (too_many_args(1, args, errmsg, NULL))
5901 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005902 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005903 }
5904 else if (strcmp(args[cur_arg], "send-state") == 0) {
5905 /* enable emission of the apparent state of a server in HTTP checks */
5906 curpx->options2 |= PR_O2_CHK_SNDST;
5907 if (too_many_args(1, args, errmsg, NULL))
5908 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005909 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005910 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005911
Christopher Faulete5870d82020-04-15 11:32:03 +02005912 /* Deduce the ruleset name from the proxy info */
5913 chunk_printf(&trash, "*http-check-%s_%s-%d",
5914 ((curpx == defpx) ? "defaults" : curpx->id),
5915 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005916
Christopher Faulet61cc8522020-04-20 14:54:42 +02005917 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005918 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005919 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005920 if (rs == NULL) {
5921 memprintf(errmsg, "out of memory.\n");
5922 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005923 }
5924 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005925
Christopher Faulete5870d82020-04-15 11:32:03 +02005926 index = 0;
5927 if (!LIST_ISEMPTY(&rs->rules)) {
5928 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5929 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5930 index = chk->index + 1;
5931 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005932
Christopher Faulete5870d82020-04-15 11:32:03 +02005933 if (strcmp(args[cur_arg], "connect") == 0)
5934 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5935 else if (strcmp(args[cur_arg], "send") == 0)
5936 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5937 else if (strcmp(args[cur_arg], "expect") == 0)
5938 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5939 file, line, errmsg);
5940 else if (strcmp(args[cur_arg], "comment") == 0)
5941 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5942 else {
5943 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005944
Christopher Faulete5870d82020-04-15 11:32:03 +02005945 if (!kw) {
5946 action_kw_tcp_check_build_list(&trash);
5947 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5948 " 'send', 'expect'%s%s. but got '%s'",
5949 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5950 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005951 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005952 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5953 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005954
Christopher Faulete5870d82020-04-15 11:32:03 +02005955 if (!chk) {
5956 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5957 goto error;
5958 }
5959 ret = (*errmsg != NULL); /* Handle warning */
5960
5961 chk->index = index;
5962 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5963 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5964 /* Use this ruleset if the proxy already has http-check enabled */
5965 curpx->tcpcheck_rules.list = &rs->rules;
5966 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5967 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5968 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5969 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005970 goto error;
5971 }
5972 }
5973 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005974 /* mark this ruleset as unused for now */
5975 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5976 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005977 }
5978
Christopher Faulete5870d82020-04-15 11:32:03 +02005979 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005980 return ret;
5981
5982 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005983 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005984 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005985 return -1;
5986}
5987
Christopher Faulete9111b62020-04-09 18:12:08 +02005988/* Parses the "external-check" proxy keyword */
5989static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5990 struct proxy *defpx, const char *file, int line,
5991 char **errmsg)
5992{
5993 int cur_arg, ret = 0;
5994
5995 cur_arg = 1;
5996 if (!*(args[cur_arg])) {
5997 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5998 goto error;
5999 }
6000
6001 if (strcmp(args[cur_arg], "command") == 0) {
6002 if (too_many_args(2, args, errmsg, NULL))
6003 goto error;
6004 if (!*(args[cur_arg+1])) {
6005 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
6006 goto error;
6007 }
6008 free(curpx->check_command);
6009 curpx->check_command = strdup(args[cur_arg+1]);
6010 }
6011 else if (strcmp(args[cur_arg], "path") == 0) {
6012 if (too_many_args(2, args, errmsg, NULL))
6013 goto error;
6014 if (!*(args[cur_arg+1])) {
6015 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
6016 goto error;
6017 }
6018 free(curpx->check_path);
6019 curpx->check_path = strdup(args[cur_arg+1]);
6020 }
6021 else {
6022 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
6023 args[0], args[1]);
6024 goto error;
6025 }
6026
6027 ret = (*errmsg != NULL); /* Handle warning */
6028 return ret;
6029
6030error:
6031 return -1;
6032}
Christopher Faulet33f05df2020-04-01 11:08:50 +02006033
Christopher Faulet430e4802020-04-09 15:28:16 +02006034/* Parses the "option tcp-check" proxy keyword */
6035int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6036 const char *file, int line)
6037{
Christopher Faulet404f9192020-04-09 23:13:54 +02006038 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02006039 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6040 int err_code = 0;
6041
6042 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6043 err_code |= ERR_WARN;
6044
6045 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6046 goto out;
6047
Christopher Faulet404f9192020-04-09 23:13:54 +02006048 curpx->options2 &= ~PR_O2_CHK_ANY;
6049 curpx->options2 |= PR_O2_TCPCHK_CHK;
6050
Christopher Fauletd7e63962020-04-17 20:15:59 +02006051 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02006052 /* If a tcp-check rulesset is already set, do nothing */
6053 if (rules->list)
6054 goto out;
6055
6056 /* If a tcp-check ruleset is waiting to be used for the current proxy,
6057 * get it.
6058 */
6059 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
6060 goto curpx_ruleset;
6061
6062 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
6063 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006064 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006065 if (rs)
6066 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02006067 }
6068
Christopher Faulet404f9192020-04-09 23:13:54 +02006069 curpx_ruleset:
6070 /* Deduce the ruleset name from the proxy info */
6071 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
6072 ((curpx == defpx) ? "defaults" : curpx->id),
6073 curpx->conf.file, curpx->conf.line);
6074
Christopher Faulet61cc8522020-04-20 14:54:42 +02006075 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006076 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006077 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006078 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02006079 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6080 goto error;
6081 }
Christopher Faulet430e4802020-04-09 15:28:16 +02006082 }
6083
Christopher Faulet404f9192020-04-09 23:13:54 +02006084 ruleset_found:
6085 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02006086 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02006087 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02006088
6089 out:
6090 return err_code;
6091
6092 error:
6093 err_code |= ERR_ALERT | ERR_FATAL;
6094 goto out;
6095}
Christopher Faulet33f05df2020-04-01 11:08:50 +02006096
6097/* Parses the "option redis-check" proxy keyword */
6098int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6099 const char *file, int line)
6100{
6101 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
6102 static char *redis_res = "+PONG\r\n";
6103
6104 struct tcpcheck_ruleset *rs = NULL;
6105 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6106 struct tcpcheck_rule *chk;
6107 char *errmsg = NULL;
6108 int err_code = 0;
6109
6110 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6111 err_code |= ERR_WARN;
6112
6113 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6114 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02006115
6116 curpx->options2 &= ~PR_O2_CHK_ANY;
6117 curpx->options2 |= PR_O2_TCPCHK_CHK;
6118
6119 free_tcpcheck_vars(&rules->preset_vars);
6120 rules->list = NULL;
6121 rules->flags = 0;
6122
Christopher Faulet61cc8522020-04-20 14:54:42 +02006123 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02006124 if (rs)
6125 goto ruleset_found;
6126
Christopher Faulet61cc8522020-04-20 14:54:42 +02006127 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02006128 if (rs == NULL) {
6129 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6130 goto error;
6131 }
6132
6133 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
6134 1, curpx, &rs->rules, file, line, &errmsg);
6135 if (!chk) {
6136 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6137 goto error;
6138 }
6139 chk->index = 0;
6140 LIST_ADDQ(&rs->rules, &chk->list);
6141
6142 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
6143 "error-status", "L7STS",
Christopher Faulet78f371e2020-04-30 09:38:08 +02006144 "on-error", "%[check.payload(0,0),cut_crlf]",
Christopher Faulet33f05df2020-04-01 11:08:50 +02006145 "on-success", "Redis server is ok",
6146 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006147 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006148 if (!chk) {
6149 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6150 goto error;
6151 }
6152 chk->index = 1;
6153 LIST_ADDQ(&rs->rules, &chk->list);
6154
Christopher Fauletd7cee712020-04-21 13:45:00 +02006155 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006156
6157 ruleset_found:
6158 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006159 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02006160
6161 out:
6162 free(errmsg);
6163 return err_code;
6164
6165 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006166 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006167 err_code |= ERR_ALERT | ERR_FATAL;
6168 goto out;
6169}
6170
Christopher Faulet811f78c2020-04-01 11:10:27 +02006171
6172/* Parses the "option ssl-hello-chk" proxy keyword */
6173int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6174 const char *file, int line)
6175{
6176 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
6177 * ssl-hello-chk option to ensure that the remote server speaks SSL.
6178 *
6179 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
6180 */
6181 static char sslv3_client_hello[] = {
6182 "16" /* ContentType : 0x16 = Hanshake */
6183 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
6184 "0079" /* ContentLength : 0x79 bytes after this one */
6185 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
6186 "000075" /* HandshakeLength : 0x75 bytes after this one */
6187 "0300" /* Hello Version : 0x0300 = v3 */
6188 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
6189 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
6190 "00" /* Session ID length : empty (no session ID) */
6191 "004E" /* Cipher Suite Length : 78 bytes after this one */
6192 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
6193 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
6194 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
6195 "000D" "000E" "000F" "0010" /* various bit lengths, */
6196 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
6197 "0015" "0016" "0017" "0018"
6198 "0019" "001A" "001B" "002F"
6199 "0030" "0031" "0032" "0033"
6200 "0034" "0035" "0036" "0037"
6201 "0038" "0039" "003A"
6202 "01" /* Compression Length : 0x01 = 1 byte for types */
6203 "00" /* Compression Type : 0x00 = NULL compression */
6204 };
6205
6206 struct tcpcheck_ruleset *rs = NULL;
6207 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6208 struct tcpcheck_rule *chk;
6209 char *errmsg = NULL;
6210 int err_code = 0;
6211
6212 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6213 err_code |= ERR_WARN;
6214
6215 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6216 goto out;
6217
Christopher Faulet811f78c2020-04-01 11:10:27 +02006218 curpx->options2 &= ~PR_O2_CHK_ANY;
6219 curpx->options2 |= PR_O2_TCPCHK_CHK;
6220
6221 free_tcpcheck_vars(&rules->preset_vars);
6222 rules->list = NULL;
6223 rules->flags = 0;
6224
Christopher Faulet61cc8522020-04-20 14:54:42 +02006225 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02006226 if (rs)
6227 goto ruleset_found;
6228
Christopher Faulet61cc8522020-04-20 14:54:42 +02006229 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02006230 if (rs == NULL) {
6231 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6232 goto error;
6233 }
6234
6235 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
6236 1, curpx, &rs->rules, file, line, &errmsg);
6237 if (!chk) {
6238 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6239 goto error;
6240 }
6241 chk->index = 0;
6242 LIST_ADDQ(&rs->rules, &chk->list);
6243
6244 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02006245 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02006246 "error-status", "L6RSP", "tout-status", "L6TOUT",
6247 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006248 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006249 if (!chk) {
6250 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6251 goto error;
6252 }
6253 chk->index = 1;
6254 LIST_ADDQ(&rs->rules, &chk->list);
6255
Christopher Fauletd7cee712020-04-21 13:45:00 +02006256 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006257
6258 ruleset_found:
6259 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006260 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02006261
6262 out:
6263 free(errmsg);
6264 return err_code;
6265
6266 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006267 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006268 err_code |= ERR_ALERT | ERR_FATAL;
6269 goto out;
6270}
6271
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006272/* Parses the "option smtpchk" proxy keyword */
6273int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6274 const char *file, int line)
6275{
6276 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
6277
6278 struct tcpcheck_ruleset *rs = NULL;
6279 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6280 struct tcpcheck_rule *chk;
6281 struct tcpcheck_var *var = NULL;
6282 char *cmd = NULL, *errmsg = NULL;
6283 int err_code = 0;
6284
6285 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6286 err_code |= ERR_WARN;
6287
6288 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6289 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006290
6291 curpx->options2 &= ~PR_O2_CHK_ANY;
6292 curpx->options2 |= PR_O2_TCPCHK_CHK;
6293
6294 free_tcpcheck_vars(&rules->preset_vars);
6295 rules->list = NULL;
6296 rules->flags = 0;
6297
6298 cur_arg += 2;
6299 if (*args[cur_arg] && *args[cur_arg+1] &&
6300 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6301 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6302 if (cmd)
6303 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6304 }
6305 else {
6306 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6307 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6308 cmd = strdup("HELO localhost");
6309 }
6310
Christopher Fauletb61caf42020-04-21 10:57:42 +02006311 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006312 if (cmd == NULL || var == NULL) {
6313 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6314 goto error;
6315 }
6316 var->data.type = SMP_T_STR;
6317 var->data.u.str.area = cmd;
6318 var->data.u.str.data = strlen(cmd);
6319 LIST_INIT(&var->list);
6320 LIST_ADDQ(&rules->preset_vars, &var->list);
6321 cmd = NULL;
6322 var = NULL;
6323
Christopher Faulet61cc8522020-04-20 14:54:42 +02006324 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006325 if (rs)
6326 goto ruleset_found;
6327
Christopher Faulet61cc8522020-04-20 14:54:42 +02006328 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006329 if (rs == NULL) {
6330 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6331 goto error;
6332 }
6333
6334 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6335 1, curpx, &rs->rules, file, line, &errmsg);
6336 if (!chk) {
6337 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6338 goto error;
6339 }
6340 chk->index = 0;
6341 LIST_ADDQ(&rs->rules, &chk->list);
6342
6343 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6344 "min-recv", "4",
6345 "error-status", "L7RSP",
Christopher Faulet78f371e2020-04-30 09:38:08 +02006346 "on-error", "%[check.payload(0,0),cut_crlf]",
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006347 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006348 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006349 if (!chk) {
6350 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6351 goto error;
6352 }
6353 chk->index = 1;
6354 LIST_ADDQ(&rs->rules, &chk->list);
6355
6356 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6357 "min-recv", "4",
6358 "error-status", "L7STS",
6359 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6360 "status-code", "check.payload(0,3)",
6361 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006362 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006363 if (!chk) {
6364 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6365 goto error;
6366 }
6367 chk->index = 2;
6368 LIST_ADDQ(&rs->rules, &chk->list);
6369
6370 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6371 1, curpx, &rs->rules, file, line, &errmsg);
6372 if (!chk) {
6373 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6374 goto error;
6375 }
6376 chk->index = 3;
6377 LIST_ADDQ(&rs->rules, &chk->list);
6378
6379 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6380 "min-recv", "4",
6381 "error-status", "L7STS",
6382 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6383 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6384 "status-code", "check.payload(0,3)",
6385 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006386 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006387 if (!chk) {
6388 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6389 goto error;
6390 }
6391 chk->index = 4;
6392 LIST_ADDQ(&rs->rules, &chk->list);
6393
Christopher Fauletd7cee712020-04-21 13:45:00 +02006394 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006395
6396 ruleset_found:
6397 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006398 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006399
6400 out:
6401 free(errmsg);
6402 return err_code;
6403
6404 error:
6405 free(cmd);
6406 free(var);
6407 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006408 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006409 err_code |= ERR_ALERT | ERR_FATAL;
6410 goto out;
6411}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006412
Christopher Fauletce355072020-04-02 11:44:39 +02006413/* Parses the "option pgsql-check" proxy keyword */
6414int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6415 const char *file, int line)
6416{
6417 static char pgsql_req[] = {
6418 "%[var(check.plen),htonl,hex]" /* The packet length*/
6419 "00030000" /* the version 3.0 */
6420 "7573657200" /* "user" key */
6421 "%[var(check.username),hex]00" /* the username */
6422 "00"
6423 };
6424
6425 struct tcpcheck_ruleset *rs = NULL;
6426 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6427 struct tcpcheck_rule *chk;
6428 struct tcpcheck_var *var = NULL;
6429 char *user = NULL, *errmsg = NULL;
6430 size_t packetlen = 0;
6431 int err_code = 0;
6432
6433 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6434 err_code |= ERR_WARN;
6435
6436 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6437 goto out;
6438
Christopher Fauletce355072020-04-02 11:44:39 +02006439 curpx->options2 &= ~PR_O2_CHK_ANY;
6440 curpx->options2 |= PR_O2_TCPCHK_CHK;
6441
6442 free_tcpcheck_vars(&rules->preset_vars);
6443 rules->list = NULL;
6444 rules->flags = 0;
6445
6446 cur_arg += 2;
6447 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6448 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6449 file, line, args[0], args[1]);
6450 goto error;
6451 }
6452 if (strcmp(args[cur_arg], "user") == 0) {
6453 packetlen = 15 + strlen(args[cur_arg+1]);
6454 user = strdup(args[cur_arg+1]);
6455
Christopher Fauletb61caf42020-04-21 10:57:42 +02006456 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006457 if (user == NULL || var == NULL) {
6458 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6459 goto error;
6460 }
6461 var->data.type = SMP_T_STR;
6462 var->data.u.str.area = user;
6463 var->data.u.str.data = strlen(user);
6464 LIST_INIT(&var->list);
6465 LIST_ADDQ(&rules->preset_vars, &var->list);
6466 user = NULL;
6467 var = NULL;
6468
Christopher Fauletb61caf42020-04-21 10:57:42 +02006469 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006470 if (var == NULL) {
6471 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6472 goto error;
6473 }
6474 var->data.type = SMP_T_SINT;
6475 var->data.u.sint = packetlen;
6476 LIST_INIT(&var->list);
6477 LIST_ADDQ(&rules->preset_vars, &var->list);
6478 var = NULL;
6479 }
6480 else {
6481 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6482 file, line, args[0], args[1]);
6483 goto error;
6484 }
6485
Christopher Faulet61cc8522020-04-20 14:54:42 +02006486 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006487 if (rs)
6488 goto ruleset_found;
6489
Christopher Faulet61cc8522020-04-20 14:54:42 +02006490 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006491 if (rs == NULL) {
6492 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6493 goto error;
6494 }
6495
6496 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6497 1, curpx, &rs->rules, file, line, &errmsg);
6498 if (!chk) {
6499 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6500 goto error;
6501 }
6502 chk->index = 0;
6503 LIST_ADDQ(&rs->rules, &chk->list);
6504
6505 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6506 1, curpx, &rs->rules, file, line, &errmsg);
6507 if (!chk) {
6508 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6509 goto error;
6510 }
6511 chk->index = 1;
6512 LIST_ADDQ(&rs->rules, &chk->list);
6513
6514 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6515 "min-recv", "5",
6516 "error-status", "L7RSP",
6517 "on-error", "%[check.payload(6,0)]",
6518 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006519 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006520 if (!chk) {
6521 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6522 goto error;
6523 }
6524 chk->index = 2;
6525 LIST_ADDQ(&rs->rules, &chk->list);
6526
Christopher Fauletb841c742020-04-27 18:29:49 +02006527 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 +02006528 "min-recv", "9",
6529 "error-status", "L7STS",
6530 "on-success", "PostgreSQL server is ok",
6531 "on-error", "PostgreSQL unknown error",
6532 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006533 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006534 if (!chk) {
6535 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6536 goto error;
6537 }
6538 chk->index = 3;
6539 LIST_ADDQ(&rs->rules, &chk->list);
6540
Christopher Fauletd7cee712020-04-21 13:45:00 +02006541 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006542
6543 ruleset_found:
6544 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006545 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006546
6547 out:
6548 free(errmsg);
6549 return err_code;
6550
6551 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006552 free(user);
6553 free(var);
6554 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006555 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006556 err_code |= ERR_ALERT | ERR_FATAL;
6557 goto out;
6558}
6559
6560
6561/* Parses the "option mysql-check" proxy keyword */
6562int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6563 const char *file, int line)
6564{
6565 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6566 * const char mysql40_client_auth_pkt[] = {
6567 * "\x0e\x00\x00" // packet length
6568 * "\x01" // packet number
6569 * "\x00\x00" // client capabilities
6570 * "\x00\x00\x01" // max packet
6571 * "haproxy\x00" // username (null terminated string)
6572 * "\x00" // filler (always 0x00)
6573 * "\x01\x00\x00" // packet length
6574 * "\x00" // packet number
6575 * "\x01" // COM_QUIT command
6576 * };
6577 */
6578 static char mysql40_rsname[] = "*mysql40-check";
6579 static char mysql40_req[] = {
6580 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6581 "0080" /* client capabilities */
6582 "000001" /* max packet */
6583 "%[var(check.username),hex]00" /* the username */
6584 "00" /* filler (always 0x00) */
6585 "010000" /* packet length*/
6586 "00" /* sequence ID */
6587 "01" /* COM_QUIT command */
6588 };
6589
6590 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6591 * const char mysql41_client_auth_pkt[] = {
6592 * "\x0e\x00\x00\" // packet length
6593 * "\x01" // packet number
6594 * "\x00\x00\x00\x00" // client capabilities
6595 * "\x00\x00\x00\x01" // max packet
6596 * "\x21" // character set (UTF-8)
6597 * char[23] // All zeroes
6598 * "haproxy\x00" // username (null terminated string)
6599 * "\x00" // filler (always 0x00)
6600 * "\x01\x00\x00" // packet length
6601 * "\x00" // packet number
6602 * "\x01" // COM_QUIT command
6603 * };
6604 */
6605 static char mysql41_rsname[] = "*mysql41-check";
6606 static char mysql41_req[] = {
6607 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6608 "00820000" /* client capabilities */
6609 "00800001" /* max packet */
6610 "21" /* character set (UTF-8) */
6611 "000000000000000000000000" /* 23 bytes, al zeroes */
6612 "0000000000000000000000"
6613 "%[var(check.username),hex]00" /* the username */
6614 "00" /* filler (always 0x00) */
6615 "010000" /* packet length*/
6616 "00" /* sequence ID */
6617 "01" /* COM_QUIT command */
6618 };
6619
6620 struct tcpcheck_ruleset *rs = NULL;
6621 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6622 struct tcpcheck_rule *chk;
6623 struct tcpcheck_var *var = NULL;
6624 char *mysql_rsname = "*mysql-check";
6625 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6626 int index = 0, err_code = 0;
6627
6628 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6629 err_code |= ERR_WARN;
6630
6631 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6632 goto out;
6633
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006634 curpx->options2 &= ~PR_O2_CHK_ANY;
6635 curpx->options2 |= PR_O2_TCPCHK_CHK;
6636
6637 free_tcpcheck_vars(&rules->preset_vars);
6638 rules->list = NULL;
6639 rules->flags = 0;
6640
6641 cur_arg += 2;
6642 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006643 int packetlen, userlen;
6644
6645 if (strcmp(args[cur_arg], "user") != 0) {
6646 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6647 file, line, args[0], args[1], args[cur_arg]);
6648 goto error;
6649 }
6650
6651 if (*(args[cur_arg+1]) == 0) {
6652 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6653 file, line, args[0], args[1], args[cur_arg]);
6654 goto error;
6655 }
6656
6657 hdr = calloc(4, sizeof(*hdr));
6658 user = strdup(args[cur_arg+1]);
6659 userlen = strlen(args[cur_arg+1]);
6660
6661 if (hdr == NULL || user == NULL) {
6662 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6663 goto error;
6664 }
6665
6666 if (*args[cur_arg+2]) {
6667 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6668 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6669 file, line, args[cur_arg], args[cur_arg+2]);
6670 goto error;
6671 }
6672 packetlen = userlen + 7 + 27;
6673 mysql_req = mysql41_req;
6674 mysql_rsname = mysql41_rsname;
6675 }
6676 else {
6677 packetlen = userlen + 7;
6678 mysql_req = mysql40_req;
6679 mysql_rsname = mysql40_rsname;
6680 }
6681
6682 hdr[0] = (unsigned char)(packetlen & 0xff);
6683 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6684 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6685 hdr[3] = 1;
6686
Christopher Fauletb61caf42020-04-21 10:57:42 +02006687 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006688 if (var == NULL) {
6689 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6690 goto error;
6691 }
6692 var->data.type = SMP_T_STR;
6693 var->data.u.str.area = hdr;
6694 var->data.u.str.data = 4;
6695 LIST_INIT(&var->list);
6696 LIST_ADDQ(&rules->preset_vars, &var->list);
6697 hdr = NULL;
6698 var = NULL;
6699
Christopher Fauletb61caf42020-04-21 10:57:42 +02006700 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006701 if (var == NULL) {
6702 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6703 goto error;
6704 }
6705 var->data.type = SMP_T_STR;
6706 var->data.u.str.area = user;
6707 var->data.u.str.data = strlen(user);
6708 LIST_INIT(&var->list);
6709 LIST_ADDQ(&rules->preset_vars, &var->list);
6710 user = NULL;
6711 var = NULL;
6712 }
6713
Christopher Faulet61cc8522020-04-20 14:54:42 +02006714 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006715 if (rs)
6716 goto ruleset_found;
6717
Christopher Faulet61cc8522020-04-20 14:54:42 +02006718 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006719 if (rs == NULL) {
6720 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6721 goto error;
6722 }
6723
6724 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6725 1, curpx, &rs->rules, file, line, &errmsg);
6726 if (!chk) {
6727 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6728 goto error;
6729 }
6730 chk->index = index++;
6731 LIST_ADDQ(&rs->rules, &chk->list);
6732
6733 if (mysql_req) {
6734 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6735 1, curpx, &rs->rules, file, line, &errmsg);
6736 if (!chk) {
6737 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6738 goto error;
6739 }
6740 chk->index = index++;
6741 LIST_ADDQ(&rs->rules, &chk->list);
6742 }
6743
6744 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006745 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006746 if (!chk) {
6747 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6748 goto error;
6749 }
6750 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6751 chk->index = index++;
6752 LIST_ADDQ(&rs->rules, &chk->list);
6753
6754 if (mysql_req) {
6755 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006756 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006757 if (!chk) {
6758 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6759 goto error;
6760 }
6761 chk->expect.custom = tcpcheck_mysql_expect_ok;
6762 chk->index = index++;
6763 LIST_ADDQ(&rs->rules, &chk->list);
6764 }
6765
Christopher Fauletd7cee712020-04-21 13:45:00 +02006766 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006767
6768 ruleset_found:
6769 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006770 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006771
6772 out:
6773 free(errmsg);
6774 return err_code;
6775
6776 error:
6777 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006778 free(user);
6779 free(var);
6780 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006781 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006782 err_code |= ERR_ALERT | ERR_FATAL;
6783 goto out;
6784}
6785
Christopher Faulet1997eca2020-04-03 23:13:50 +02006786int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6787 const char *file, int line)
6788{
6789 static char *ldap_req = "300C020101600702010304008000";
6790
6791 struct tcpcheck_ruleset *rs = NULL;
6792 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6793 struct tcpcheck_rule *chk;
6794 char *errmsg = NULL;
6795 int err_code = 0;
6796
6797 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6798 err_code |= ERR_WARN;
6799
6800 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6801 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006802
6803 curpx->options2 &= ~PR_O2_CHK_ANY;
6804 curpx->options2 |= PR_O2_TCPCHK_CHK;
6805
6806 free_tcpcheck_vars(&rules->preset_vars);
6807 rules->list = NULL;
6808 rules->flags = 0;
6809
Christopher Faulet61cc8522020-04-20 14:54:42 +02006810 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006811 if (rs)
6812 goto ruleset_found;
6813
Christopher Faulet61cc8522020-04-20 14:54:42 +02006814 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006815 if (rs == NULL) {
6816 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6817 goto error;
6818 }
6819
6820 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6821 1, curpx, &rs->rules, file, line, &errmsg);
6822 if (!chk) {
6823 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6824 goto error;
6825 }
6826 chk->index = 0;
6827 LIST_ADDQ(&rs->rules, &chk->list);
6828
6829 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6830 "min-recv", "14",
6831 "on-error", "Not LDAPv3 protocol",
6832 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006833 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006834 if (!chk) {
6835 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6836 goto error;
6837 }
6838 chk->index = 1;
6839 LIST_ADDQ(&rs->rules, &chk->list);
6840
6841 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006842 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006843 if (!chk) {
6844 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6845 goto error;
6846 }
6847 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6848 chk->index = 2;
6849 LIST_ADDQ(&rs->rules, &chk->list);
6850
Christopher Fauletd7cee712020-04-21 13:45:00 +02006851 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006852
6853 ruleset_found:
6854 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006855 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006856
6857 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006858 free(errmsg);
6859 return err_code;
6860
6861 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006862 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006863 err_code |= ERR_ALERT | ERR_FATAL;
6864 goto out;
6865}
6866
6867int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6868 const char *file, int line)
6869{
6870 struct tcpcheck_ruleset *rs = NULL;
6871 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6872 struct tcpcheck_rule *chk;
6873 char *spop_req = NULL;
6874 char *errmsg = NULL;
6875 int spop_len = 0, err_code = 0;
6876
6877 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6878 err_code |= ERR_WARN;
6879
6880 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6881 goto out;
6882
Christopher Faulet267b01b2020-04-04 10:27:09 +02006883 curpx->options2 &= ~PR_O2_CHK_ANY;
6884 curpx->options2 |= PR_O2_TCPCHK_CHK;
6885
6886 free_tcpcheck_vars(&rules->preset_vars);
6887 rules->list = NULL;
6888 rules->flags = 0;
6889
6890
Christopher Faulet61cc8522020-04-20 14:54:42 +02006891 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006892 if (rs)
6893 goto ruleset_found;
6894
Christopher Faulet61cc8522020-04-20 14:54:42 +02006895 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006896 if (rs == NULL) {
6897 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6898 goto error;
6899 }
6900
6901 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6902 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6903 goto error;
6904 }
6905 chunk_reset(&trash);
6906 dump_binary(&trash, spop_req, spop_len);
6907 trash.area[trash.data] = '\0';
6908
6909 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6910 1, curpx, &rs->rules, file, line, &errmsg);
6911 if (!chk) {
6912 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6913 goto error;
6914 }
6915 chk->index = 0;
6916 LIST_ADDQ(&rs->rules, &chk->list);
6917
6918 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006919 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006920 if (!chk) {
6921 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6922 goto error;
6923 }
6924 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6925 chk->index = 1;
6926 LIST_ADDQ(&rs->rules, &chk->list);
6927
Christopher Fauletd7cee712020-04-21 13:45:00 +02006928 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006929
6930 ruleset_found:
6931 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006932 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006933
6934 out:
6935 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006936 free(errmsg);
6937 return err_code;
6938
6939 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006940 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006941 err_code |= ERR_ALERT | ERR_FATAL;
6942 goto out;
6943}
Christopher Fauletce355072020-04-02 11:44:39 +02006944
Christopher Faulete5870d82020-04-15 11:32:03 +02006945
6946struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6947{
6948 struct tcpcheck_rule *chk = NULL;
6949 struct tcpcheck_http_hdr *hdr = NULL;
6950 char *meth = NULL, *uri = NULL, *vsn = NULL;
6951 char *hdrs, *body;
6952
6953 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6954 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6955 if (hdrs == body)
6956 hdrs = NULL;
6957 if (hdrs) {
6958 *hdrs = '\0';
6959 hdrs +=2;
6960 }
6961 if (body) {
6962 *body = '\0';
6963 body += 4;
6964 }
6965 if (hdrs || body) {
6966 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6967 " Please, consider to use 'http-check send' directive instead.");
6968 }
6969
6970 chk = calloc(1, sizeof(*chk));
6971 if (!chk) {
6972 memprintf(errmsg, "out of memory");
6973 goto error;
6974 }
6975 chk->action = TCPCHK_ACT_SEND;
6976 chk->send.type = TCPCHK_SEND_HTTP;
6977 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6978 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6979 LIST_INIT(&chk->send.http.hdrs);
6980
6981 /* Copy the method, uri and version */
6982 if (*args[cur_arg]) {
6983 if (!*args[cur_arg+1])
6984 uri = args[cur_arg];
6985 else
6986 meth = args[cur_arg];
6987 }
6988 if (*args[cur_arg+1])
6989 uri = args[cur_arg+1];
6990 if (*args[cur_arg+2])
6991 vsn = args[cur_arg+2];
6992
6993 if (meth) {
6994 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6995 chk->send.http.meth.str.area = strdup(meth);
6996 chk->send.http.meth.str.data = strlen(meth);
6997 if (!chk->send.http.meth.str.area) {
6998 memprintf(errmsg, "out of memory");
6999 goto error;
7000 }
7001 }
7002 if (uri) {
7003 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007004 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007005 memprintf(errmsg, "out of memory");
7006 goto error;
7007 }
7008 }
7009 if (vsn) {
7010 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007011 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007012 memprintf(errmsg, "out of memory");
7013 goto error;
7014 }
7015 }
7016
7017 /* Copy the header */
7018 if (hdrs) {
7019 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
7020 struct h1m h1m;
7021 int i, ret;
7022
7023 /* Build and parse the request */
7024 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
7025
7026 h1m.flags = H1_MF_HDRS_ONLY;
7027 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
7028 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
7029 &h1m, NULL);
7030 if (ret <= 0) {
7031 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
7032 goto error;
7033 }
7034
Christopher Fauletb61caf42020-04-21 10:57:42 +02007035 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007036 hdr = calloc(1, sizeof(*hdr));
7037 if (!hdr) {
7038 memprintf(errmsg, "out of memory");
7039 goto error;
7040 }
7041 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007042 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02007043 if (!hdr->name.ptr) {
7044 memprintf(errmsg, "out of memory");
7045 goto error;
7046 }
7047
Christopher Fauletb61caf42020-04-21 10:57:42 +02007048 ist0(tmp_hdrs[i].v);
7049 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 +02007050 goto error;
7051 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
7052 }
7053 }
7054
7055 /* Copy the body */
7056 if (body) {
7057 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007058 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007059 memprintf(errmsg, "out of memory");
7060 goto error;
7061 }
7062 }
7063
7064 return chk;
7065
7066 error:
7067 free_tcpcheck_http_hdr(hdr);
7068 free_tcpcheck(chk, 0);
7069 return NULL;
7070}
7071
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007072int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
7073 const char *file, int line)
7074{
Christopher Faulete5870d82020-04-15 11:32:03 +02007075 struct tcpcheck_ruleset *rs = NULL;
7076 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
7077 struct tcpcheck_rule *chk;
7078 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007079 int err_code = 0;
7080
7081 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
7082 err_code |= ERR_WARN;
7083
7084 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
7085 goto out;
7086
Christopher Faulete5870d82020-04-15 11:32:03 +02007087 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
7088 if (!chk) {
7089 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
7090 goto error;
7091 }
7092 if (errmsg) {
7093 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
7094 err_code |= ERR_WARN;
7095 free(errmsg);
7096 errmsg = NULL;
7097 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007098
Christopher Faulete5870d82020-04-15 11:32:03 +02007099 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007100 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02007101 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007102
Christopher Faulete5870d82020-04-15 11:32:03 +02007103 free_tcpcheck_vars(&rules->preset_vars);
7104 rules->list = NULL;
7105 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007106
Christopher Faulete5870d82020-04-15 11:32:03 +02007107 /* Deduce the ruleset name from the proxy info */
7108 chunk_printf(&trash, "*http-check-%s_%s-%d",
7109 ((curpx == defpx) ? "defaults" : curpx->id),
7110 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007111
Christopher Faulet61cc8522020-04-20 14:54:42 +02007112 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02007113 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02007114 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02007115 if (rs == NULL) {
7116 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
7117 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007118 }
Christopher Faulete5870d82020-04-15 11:32:03 +02007119 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007120
Christopher Faulete5870d82020-04-15 11:32:03 +02007121 rules->list = &rs->rules;
7122 rules->flags |= TCPCHK_RULES_HTTP_CHK;
7123 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
7124 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
7125 rules->list = NULL;
7126 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007127 }
Christopher Faulete5870d82020-04-15 11:32:03 +02007128
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007129 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02007130 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007131 return err_code;
7132
7133 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02007134 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02007135 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007136 err_code |= ERR_ALERT | ERR_FATAL;
7137 goto out;
7138}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007139
Christopher Faulet6f557912020-04-09 15:58:50 +02007140int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
7141 const char *file, int line)
7142{
7143 int err_code = 0;
7144
Christopher Faulet6f557912020-04-09 15:58:50 +02007145 curpx->options2 &= ~PR_O2_CHK_ANY;
7146 curpx->options2 |= PR_O2_EXT_CHK;
7147 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
7148 goto out;
7149
7150 out:
7151 return err_code;
7152}
7153
Christopher Fauletce8111e2020-04-06 15:04:11 +02007154/* Parse the "addr" server keyword */
7155static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7156 char **errmsg)
7157{
7158 struct sockaddr_storage *sk;
7159 struct protocol *proto;
7160 int port1, port2, err_code = 0;
7161
7162
7163 if (!*args[*cur_arg+1]) {
7164 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
7165 goto error;
7166 }
7167
7168 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
7169 if (!sk) {
7170 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
7171 goto error;
7172 }
7173
7174 proto = protocol_by_family(sk->ss_family);
7175 if (!proto || !proto->connect) {
7176 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
7177 args[*cur_arg], args[*cur_arg+1]);
7178 goto error;
7179 }
7180
7181 if (port1 != port2) {
7182 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
7183 args[*cur_arg], args[*cur_arg+1]);
7184 goto error;
7185 }
7186
7187 srv->check.addr = srv->agent.addr = *sk;
7188 srv->flags |= SRV_F_CHECKADDR;
7189 srv->flags |= SRV_F_AGENTADDR;
7190
7191 out:
7192 return err_code;
7193
7194 error:
7195 err_code |= ERR_ALERT | ERR_FATAL;
7196 goto out;
7197}
7198
7199
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007200/* Parse the "agent-addr" server keyword */
7201static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7202 char **errmsg)
7203{
7204 int err_code = 0;
7205
7206 if (!*(args[*cur_arg+1])) {
7207 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
7208 goto error;
7209 }
7210 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
7211 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
7212 goto error;
7213 }
7214
7215 out:
7216 return err_code;
7217
7218 error:
7219 err_code |= ERR_ALERT | ERR_FATAL;
7220 goto out;
7221}
7222
7223/* Parse the "agent-check" server keyword */
7224static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7225 char **errmsg)
7226{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007227 struct tcpcheck_ruleset *rs = NULL;
7228 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7229 struct tcpcheck_rule *chk;
7230 int err_code = 0;
7231
7232 if (srv->do_agent)
7233 goto out;
7234
7235 if (!rules) {
7236 rules = calloc(1, sizeof(*rules));
7237 if (!rules) {
7238 memprintf(errmsg, "out of memory.");
7239 goto error;
7240 }
7241 LIST_INIT(&rules->preset_vars);
7242 srv->agent.tcpcheck_rules = rules;
7243 }
7244 rules->list = NULL;
7245 rules->flags = 0;
7246
Christopher Faulet61cc8522020-04-20 14:54:42 +02007247 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007248 if (rs)
7249 goto ruleset_found;
7250
Christopher Faulet61cc8522020-04-20 14:54:42 +02007251 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007252 if (rs == NULL) {
7253 memprintf(errmsg, "out of memory.");
7254 goto error;
7255 }
7256
7257 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
7258 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
7259 if (!chk) {
7260 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
7261 goto error;
7262 }
7263 chk->index = 0;
7264 LIST_ADDQ(&rs->rules, &chk->list);
7265
7266 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02007267 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
7268 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007269 if (!chk) {
7270 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
7271 goto error;
7272 }
7273 chk->expect.custom = tcpcheck_agent_expect_reply;
7274 chk->index = 1;
7275 LIST_ADDQ(&rs->rules, &chk->list);
7276
Christopher Fauletd7cee712020-04-21 13:45:00 +02007277 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007278
7279 ruleset_found:
7280 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007281 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007282 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007283
7284 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007285 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007286
7287 error:
7288 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02007289 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007290 err_code |= ERR_ALERT | ERR_FATAL;
7291 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007292}
7293
7294/* Parse the "agent-inter" server keyword */
7295static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7296 char **errmsg)
7297{
7298 const char *err = NULL;
7299 unsigned int delay;
7300 int err_code = 0;
7301
7302 if (!*(args[*cur_arg+1])) {
7303 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7304 goto error;
7305 }
7306
7307 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7308 if (err == PARSE_TIME_OVER) {
7309 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7310 args[*cur_arg+1], args[*cur_arg], srv->id);
7311 goto error;
7312 }
7313 else if (err == PARSE_TIME_UNDER) {
7314 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7315 args[*cur_arg+1], args[*cur_arg], srv->id);
7316 goto error;
7317 }
7318 else if (err) {
7319 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7320 *err, srv->id);
7321 goto error;
7322 }
7323 if (delay <= 0) {
7324 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7325 delay, args[*cur_arg], srv->id);
7326 goto error;
7327 }
7328 srv->agent.inter = delay;
7329
7330 out:
7331 return err_code;
7332
7333 error:
7334 err_code |= ERR_ALERT | ERR_FATAL;
7335 goto out;
7336}
7337
7338/* Parse the "agent-port" server keyword */
7339static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7340 char **errmsg)
7341{
7342 int err_code = 0;
7343
7344 if (!*(args[*cur_arg+1])) {
7345 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7346 goto error;
7347 }
7348
7349 global.maxsock++;
7350 srv->agent.port = atol(args[*cur_arg+1]);
7351
7352 out:
7353 return err_code;
7354
7355 error:
7356 err_code |= ERR_ALERT | ERR_FATAL;
7357 goto out;
7358}
7359
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007360int set_srv_agent_send(struct server *srv, const char *send)
7361{
7362 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7363 struct tcpcheck_var *var = NULL;
7364 char *str;
7365
7366 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007367 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007368 if (str == NULL || var == NULL)
7369 goto error;
7370
7371 free_tcpcheck_vars(&rules->preset_vars);
7372
7373 var->data.type = SMP_T_STR;
7374 var->data.u.str.area = str;
7375 var->data.u.str.data = strlen(str);
7376 LIST_INIT(&var->list);
7377 LIST_ADDQ(&rules->preset_vars, &var->list);
7378
7379 return 1;
7380
7381 error:
7382 free(str);
7383 free(var);
7384 return 0;
7385}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007386
7387/* Parse the "agent-send" server keyword */
7388static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7389 char **errmsg)
7390{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007391 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007392 int err_code = 0;
7393
7394 if (!*(args[*cur_arg+1])) {
7395 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7396 goto error;
7397 }
7398
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007399 if (!rules) {
7400 rules = calloc(1, sizeof(*rules));
7401 if (!rules) {
7402 memprintf(errmsg, "out of memory.");
7403 goto error;
7404 }
7405 LIST_INIT(&rules->preset_vars);
7406 srv->agent.tcpcheck_rules = rules;
7407 }
7408
7409 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007410 memprintf(errmsg, "out of memory.");
7411 goto error;
7412 }
7413
7414 out:
7415 return err_code;
7416
7417 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007418 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007419 err_code |= ERR_ALERT | ERR_FATAL;
7420 goto out;
7421}
7422
7423/* Parse the "no-agent-send" server keyword */
7424static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7425 char **errmsg)
7426{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007427 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007428 return 0;
7429}
7430
Christopher Fauletce8111e2020-04-06 15:04:11 +02007431/* Parse the "check" server keyword */
7432static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7433 char **errmsg)
7434{
7435 srv->do_check = 1;
7436 return 0;
7437}
7438
7439/* Parse the "check-send-proxy" server keyword */
7440static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7441 char **errmsg)
7442{
7443 srv->check.send_proxy = 1;
7444 return 0;
7445}
7446
7447/* Parse the "check-via-socks4" server keyword */
7448static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7449 char **errmsg)
7450{
7451 srv->check.via_socks4 = 1;
7452 return 0;
7453}
7454
7455/* Parse the "no-check" server keyword */
7456static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7457 char **errmsg)
7458{
7459 deinit_srv_check(srv);
7460 return 0;
7461}
7462
7463/* Parse the "no-check-send-proxy" server keyword */
7464static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7465 char **errmsg)
7466{
7467 srv->check.send_proxy = 0;
7468 return 0;
7469}
7470
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007471/* parse the "check-proto" server keyword */
7472static int srv_parse_check_proto(char **args, int *cur_arg,
7473 struct proxy *px, struct server *newsrv, char **err)
7474{
7475 int err_code = 0;
7476
7477 if (!*args[*cur_arg + 1]) {
7478 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7479 goto error;
7480 }
7481 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7482 if (!newsrv->check.mux_proto) {
7483 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7484 goto error;
7485 }
7486
7487 out:
7488 return err_code;
7489
7490 error:
7491 err_code |= ERR_ALERT | ERR_FATAL;
7492 goto out;
7493}
7494
7495
Christopher Fauletce8111e2020-04-06 15:04:11 +02007496/* Parse the "rise" server keyword */
7497static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7498 char **errmsg)
7499{
7500 int err_code = 0;
7501
7502 if (!*args[*cur_arg + 1]) {
7503 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7504 goto error;
7505 }
7506
7507 srv->check.rise = atol(args[*cur_arg+1]);
7508 if (srv->check.rise <= 0) {
7509 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7510 goto error;
7511 }
7512
7513 if (srv->check.health)
7514 srv->check.health = srv->check.rise;
7515
7516 out:
7517 return err_code;
7518
7519 error:
7520 deinit_srv_agent_check(srv);
7521 err_code |= ERR_ALERT | ERR_FATAL;
7522 goto out;
7523 return 0;
7524}
7525
7526/* Parse the "fall" server keyword */
7527static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7528 char **errmsg)
7529{
7530 int err_code = 0;
7531
7532 if (!*args[*cur_arg + 1]) {
7533 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7534 goto error;
7535 }
7536
7537 srv->check.fall = atol(args[*cur_arg+1]);
7538 if (srv->check.fall <= 0) {
7539 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7540 goto error;
7541 }
7542
7543 out:
7544 return err_code;
7545
7546 error:
7547 deinit_srv_agent_check(srv);
7548 err_code |= ERR_ALERT | ERR_FATAL;
7549 goto out;
7550 return 0;
7551}
7552
7553/* Parse the "inter" server keyword */
7554static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7555 char **errmsg)
7556{
7557 const char *err = NULL;
7558 unsigned int delay;
7559 int err_code = 0;
7560
7561 if (!*(args[*cur_arg+1])) {
7562 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7563 goto error;
7564 }
7565
7566 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7567 if (err == PARSE_TIME_OVER) {
7568 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7569 args[*cur_arg+1], args[*cur_arg], srv->id);
7570 goto error;
7571 }
7572 else if (err == PARSE_TIME_UNDER) {
7573 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7574 args[*cur_arg+1], args[*cur_arg], srv->id);
7575 goto error;
7576 }
7577 else if (err) {
7578 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7579 *err, srv->id);
7580 goto error;
7581 }
7582 if (delay <= 0) {
7583 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7584 delay, args[*cur_arg], srv->id);
7585 goto error;
7586 }
7587 srv->check.inter = delay;
7588
7589 out:
7590 return err_code;
7591
7592 error:
7593 err_code |= ERR_ALERT | ERR_FATAL;
7594 goto out;
7595}
7596
7597
7598/* Parse the "fastinter" server keyword */
7599static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7600 char **errmsg)
7601{
7602 const char *err = NULL;
7603 unsigned int delay;
7604 int err_code = 0;
7605
7606 if (!*(args[*cur_arg+1])) {
7607 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7608 goto error;
7609 }
7610
7611 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7612 if (err == PARSE_TIME_OVER) {
7613 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7614 args[*cur_arg+1], args[*cur_arg], srv->id);
7615 goto error;
7616 }
7617 else if (err == PARSE_TIME_UNDER) {
7618 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7619 args[*cur_arg+1], args[*cur_arg], srv->id);
7620 goto error;
7621 }
7622 else if (err) {
7623 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7624 *err, srv->id);
7625 goto error;
7626 }
7627 if (delay <= 0) {
7628 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7629 delay, args[*cur_arg], srv->id);
7630 goto error;
7631 }
7632 srv->check.fastinter = delay;
7633
7634 out:
7635 return err_code;
7636
7637 error:
7638 err_code |= ERR_ALERT | ERR_FATAL;
7639 goto out;
7640}
7641
7642
7643/* Parse the "downinter" server keyword */
7644static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7645 char **errmsg)
7646{
7647 const char *err = NULL;
7648 unsigned int delay;
7649 int err_code = 0;
7650
7651 if (!*(args[*cur_arg+1])) {
7652 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7653 goto error;
7654 }
7655
7656 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7657 if (err == PARSE_TIME_OVER) {
7658 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7659 args[*cur_arg+1], args[*cur_arg], srv->id);
7660 goto error;
7661 }
7662 else if (err == PARSE_TIME_UNDER) {
7663 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7664 args[*cur_arg+1], args[*cur_arg], srv->id);
7665 goto error;
7666 }
7667 else if (err) {
7668 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7669 *err, srv->id);
7670 goto error;
7671 }
7672 if (delay <= 0) {
7673 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7674 delay, args[*cur_arg], srv->id);
7675 goto error;
7676 }
7677 srv->check.downinter = delay;
7678
7679 out:
7680 return err_code;
7681
7682 error:
7683 err_code |= ERR_ALERT | ERR_FATAL;
7684 goto out;
7685}
7686
7687/* Parse the "port" server keyword */
7688static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7689 char **errmsg)
7690{
7691 int err_code = 0;
7692
7693 if (!*(args[*cur_arg+1])) {
7694 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7695 goto error;
7696 }
7697
7698 global.maxsock++;
7699 srv->check.port = atol(args[*cur_arg+1]);
7700 srv->flags |= SRV_F_CHECKPORT;
7701
7702 out:
7703 return err_code;
7704
7705 error:
7706 err_code |= ERR_ALERT | ERR_FATAL;
7707 goto out;
7708}
7709
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007710static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007711 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7712 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7713 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007714 { 0, NULL, NULL },
7715}};
7716
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007717static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007718 { "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 +02007719 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7720 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7721 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7722 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7723 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007724 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007725 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007726 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7727 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007728 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007729 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7730 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7731 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7732 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7733 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7734 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7735 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7736 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007737 { NULL, NULL, 0 },
7738}};
7739
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007740INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007741INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007742
Willy Tarreaubd741542010-03-16 18:46:54 +01007743/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007744 * Local variables:
7745 * c-indent-level: 8
7746 * c-basic-offset: 8
7747 * End:
7748 */