blob: c79cc1fdd87f9eefeb6bbdaf52b9d10cd5bd2355 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001205 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001659 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1660 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
1686 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001687 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001688 *conn->dst = (is_addr(&connect->addr)
1689 ? connect->addr
1690 : (is_addr(&check->addr) ? check->addr : s->addr));
1691 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001692
Christopher Faulet61cc8522020-04-20 14:54:42 +02001693 port = 0;
1694 if (!port && connect->port)
1695 port = connect->port;
1696 if (!port && connect->port_expr) {
1697 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001698
Christopher Faulet61cc8522020-04-20 14:54:42 +02001699 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1700 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1701 connect->port_expr, SMP_T_SINT);
1702 if (smp)
1703 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001704 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001705 if (!port && is_inet_addr(&connect->addr))
1706 port = get_host_port(&connect->addr);
1707 if (!port && check->port)
1708 port = check->port;
1709 if (!port && is_inet_addr(&check->addr))
1710 port = get_host_port(&check->addr);
1711 if (!port)
1712 port = s->svc_port;
1713 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001714
Christopher Faulet61cc8522020-04-20 14:54:42 +02001715 xprt = ((connect->options & TCPCHK_OPT_SSL)
1716 ? xprt_get(XPRT_SSL)
1717 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 conn_prepare(conn, proto, xprt);
1720 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 status = SF_ERR_INTERNAL;
1723 if (proto && proto->connect) {
1724 struct tcpcheck_rule *next;
1725 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001726
Christopher Faulet61cc8522020-04-20 14:54:42 +02001727 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1728 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1731 if (!next || next->action != TCPCHK_ACT_EXPECT)
1732 flags |= CONNECT_DELACK_ALWAYS;
1733 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001734 }
1735
Christopher Faulet61cc8522020-04-20 14:54:42 +02001736 if (status != SF_ERR_NONE)
1737 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 conn->flags |= CO_FL_PRIVATE;
1740 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 /* The mux may be initialized now if there isn't server attached to the
1743 * check (email alerts) or if there is a mux proto specified or if there
1744 * is no alpn.
1745 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001746 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1747 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001748 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001749
Christopher Faulet61cc8522020-04-20 14:54:42 +02001750 if (connect->mux_proto)
1751 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001752 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 mux_ops = check->mux_proto->mux;
1754 else {
1755 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1756 ? PROTO_MODE_HTTP
1757 : PROTO_MODE_TCP);
1758
1759 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001760 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001761 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1762 status = SF_ERR_INTERNAL;
1763 goto fail_check;
1764 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001765 }
1766
Christopher Faulet61cc8522020-04-20 14:54:42 +02001767#ifdef USE_OPENSSL
1768 if (connect->sni)
1769 ssl_sock_set_servername(conn, connect->sni);
1770 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1771 ssl_sock_set_servername(conn, s->check.sni);
1772
1773 if (connect->alpn)
1774 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1775 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1776 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1777#endif
1778 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1779 conn->send_proxy_ofs = 1;
1780 conn->flags |= CO_FL_SOCKS4;
1781 }
1782 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1783 conn->send_proxy_ofs = 1;
1784 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001785 }
1786
Christopher Faulet61cc8522020-04-20 14:54:42 +02001787 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1788 conn->send_proxy_ofs = 1;
1789 conn->flags |= CO_FL_SEND_PROXY;
1790 }
1791 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1792 conn->send_proxy_ofs = 1;
1793 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001794 }
1795
Christopher Faulet61cc8522020-04-20 14:54:42 +02001796 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1797 /* Some servers don't like reset on close */
1798 fdtab[cs->conn->handle.fd].linger_risk = 0;
1799 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001800
Christopher Faulet61cc8522020-04-20 14:54:42 +02001801 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1802 if (xprt_add_hs(conn) < 0)
1803 status = SF_ERR_RESOURCE;
1804 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001805
Christopher Faulet61cc8522020-04-20 14:54:42 +02001806 fail_check:
1807 /* It can return one of :
1808 * - SF_ERR_NONE if everything's OK
1809 * - SF_ERR_SRVTO if there are no more servers
1810 * - SF_ERR_SRVCL if the connection was refused by the server
1811 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1812 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1813 * - SF_ERR_INTERNAL for any other purely internal errors
1814 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1815 * Note that we try to prevent the network stack from sending the ACK during the
1816 * connect() when a pure TCP check is used (without PROXY protocol).
1817 */
1818 switch (status) {
1819 case SF_ERR_NONE:
1820 /* we allow up to min(inter, timeout.connect) for a connection
1821 * to establish but only when timeout.check is set as it may be
1822 * to short for a full check otherwise
1823 */
1824 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001825
Christopher Faulet61cc8522020-04-20 14:54:42 +02001826 if (proxy->timeout.check && proxy->timeout.connect) {
1827 int t_con = tick_add(now_ms, proxy->timeout.connect);
1828 t->expire = tick_first(t->expire, t_con);
1829 }
1830 break;
1831 case SF_ERR_SRVTO: /* ETIMEDOUT */
1832 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1833 case SF_ERR_PRXCOND:
1834 case SF_ERR_RESOURCE:
1835 case SF_ERR_INTERNAL:
1836 chk_report_conn_err(check, errno, 0);
1837 ret = TCPCHK_EVAL_STOP;
1838 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001839 }
1840
Christopher Faulet61cc8522020-04-20 14:54:42 +02001841 /* don't do anything until the connection is established */
1842 if (conn->flags & CO_FL_WAIT_XPRT) {
1843 ret = TCPCHK_EVAL_WAIT;
1844 goto out;
1845 }
1846
1847 out:
1848 if (conn && check->result == CHK_RES_FAILED)
1849 conn->flags |= CO_FL_ERROR;
1850 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001851}
1852
Christopher Faulet61cc8522020-04-20 14:54:42 +02001853/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1854 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1855 * TCPCHK_EVAL_STOP if an error occurred.
1856 */
1857static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001858{
1859 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001860 struct tcpcheck_send *send = &rule->send;
1861 struct conn_stream *cs = check->cs;
1862 struct connection *conn = cs_conn(cs);
1863 struct buffer *tmp = NULL;
1864 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001865
Christopher Faulet61cc8522020-04-20 14:54:42 +02001866 /* reset the read & write buffer */
1867 b_reset(&check->bi);
1868 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001869
Christopher Faulet61cc8522020-04-20 14:54:42 +02001870 switch (send->type) {
1871 case TCPCHK_SEND_STRING:
1872 case TCPCHK_SEND_BINARY:
1873 if (istlen(send->data) >= b_size(&check->bo)) {
1874 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1875 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1876 tcpcheck_get_step_id(check, rule));
1877 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1878 ret = TCPCHK_EVAL_STOP;
1879 goto out;
1880 }
1881 b_putist(&check->bo, send->data);
1882 break;
1883 case TCPCHK_SEND_STRING_LF:
1884 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1885 if (!b_data(&check->bo))
1886 goto out;
1887 break;
1888 case TCPCHK_SEND_BINARY_LF:
1889 tmp = alloc_trash_chunk();
1890 if (!tmp)
1891 goto error_lf;
1892 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1893 if (!b_data(tmp))
1894 goto out;
1895 tmp->area[tmp->data] = '\0';
1896 b_set_data(&check->bo, b_size(&check->bo));
1897 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1898 goto error_lf;
1899 break;
1900 case TCPCHK_SEND_HTTP: {
1901 struct htx_sl *sl;
1902 struct ist meth, uri, vsn, clen, body;
1903 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001904
Christopher Faulet61cc8522020-04-20 14:54:42 +02001905 tmp = alloc_trash_chunk();
1906 if (!tmp)
1907 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001908
Christopher Faulet61cc8522020-04-20 14:54:42 +02001909 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1910 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1911 : http_known_methods[send->http.meth.meth]);
1912 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1913 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001914
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001915 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1916 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001917 slflags |= HTX_SL_F_VER_11;
1918 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1919 if (!isttest(send->http.body))
1920 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001921
Christopher Faulet61cc8522020-04-20 14:54:42 +02001922 htx = htx_from_buf(&check->bo);
1923 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1924 if (!sl)
1925 goto error_htx;
1926 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001927
Christopher Faulet61cc8522020-04-20 14:54:42 +02001928 body = send->http.body; // TODO: handle body_fmt
1929 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001930
Christopher Faulet61cc8522020-04-20 14:54:42 +02001931 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1932 !htx_add_header(htx, ist("Content-length"), clen))
1933 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001934
Christopher Faulet61cc8522020-04-20 14:54:42 +02001935 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1936 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001937
Christopher Faulet61cc8522020-04-20 14:54:42 +02001938 list_for_each_entry(hdr, &send->http.hdrs, list) {
1939 chunk_reset(tmp);
1940 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1941 if (!b_data(tmp))
1942 continue;
1943 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1944 goto error_htx;
1945 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001946
Christopher Faulet61cc8522020-04-20 14:54:42 +02001947 }
1948 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1949 chunk_reset(tmp);
1950 httpchk_build_status_header(check->server, tmp);
1951 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1952 goto error_htx;
1953 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001954
Christopher Faulet61cc8522020-04-20 14:54:42 +02001955 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1956 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1957 !htx_add_endof(htx, HTX_BLK_EOM))
1958 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001959
Christopher Faulet61cc8522020-04-20 14:54:42 +02001960 htx_to_buf(htx, &check->bo);
1961 break;
1962 }
1963 case TCPCHK_SEND_UNDEF:
1964 /* Should never happen. */
1965 ret = TCPCHK_EVAL_STOP;
1966 goto out;
1967 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001968
Christopher Faulet6d471212020-04-22 11:09:25 +02001969
1970 if (conn->mux->snd_buf(cs, &check->bo,
1971 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001972 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001973 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001974 goto out;
1975 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001976 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001977 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001978 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1979 ret = TCPCHK_EVAL_WAIT;
1980 goto out;
1981 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001982
Christopher Faulet61cc8522020-04-20 14:54:42 +02001983 out:
1984 free_trash_chunk(tmp);
1985 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001986
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 error_htx:
1988 if (htx) {
1989 htx_reset(htx);
1990 htx_to_buf(htx, &check->bo);
1991 }
1992 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1993 tcpcheck_get_step_id(check, rule));
1994 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1995 ret = TCPCHK_EVAL_STOP;
1996 goto out;
1997
1998 error_lf:
1999 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2000 tcpcheck_get_step_id(check, rule));
2001 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2002 ret = TCPCHK_EVAL_STOP;
2003 goto out;
2004
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002005}
2006
Christopher Faulet61cc8522020-04-20 14:54:42 +02002007/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2008 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2009 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2010 * TCPCHK_EVAL_STOP if an error occurred.
2011 */
2012static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002013{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002014 struct conn_stream *cs = check->cs;
2015 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002016 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002017 size_t max, read, cur_read = 0;
2018 int is_empty;
2019 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002020
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021 if (check->wait_list.events & SUB_RETRY_RECV)
2022 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002023
Christopher Faulet61cc8522020-04-20 14:54:42 +02002024 if (cs->flags & CS_FL_EOS)
2025 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002026
Christopher Faulet61cc8522020-04-20 14:54:42 +02002027 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002028
Christopher Faulet61cc8522020-04-20 14:54:42 +02002029 /* prepare to detect if the mux needs more room */
2030 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002031
Christopher Faulet61cc8522020-04-20 14:54:42 +02002032 while ((cs->flags & CS_FL_RCV_MORE) ||
2033 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2034 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2035 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2036 cur_read += read;
2037 if (!read ||
2038 (cs->flags & CS_FL_WANT_ROOM) ||
2039 (--read_poll <= 0) ||
2040 (read < max && read >= global.tune.recv_enough))
2041 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002042 }
2043
Christopher Faulet61cc8522020-04-20 14:54:42 +02002044 end_recv:
2045 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2046 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2047 /* Report network errors only if we got no other data. Otherwise
2048 * we'll let the upper layers decide whether the response is OK
2049 * or not. It is very common that an RST sent by the server is
2050 * reported as an error just after the last data chunk.
2051 */
2052 goto stop;
2053 }
2054 if (!cur_read) {
2055 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2056 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2057 goto wait_more_data;
2058 }
2059 if (is_empty) {
2060 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2061 tcpcheck_get_step_id(check, rule));
2062 if (rule->comment)
2063 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2064 set_server_check_status(check, rule->expect.err_status, trash.area);
2065 goto stop;
2066 }
2067 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002068
2069 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002070 return ret;
2071
Christopher Faulet61cc8522020-04-20 14:54:42 +02002072 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002073 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002074 goto out;
2075
2076 wait_more_data:
2077 ret = TCPCHK_EVAL_WAIT;
2078 goto out;
2079}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002080
Christopher Faulet61cc8522020-04-20 14:54:42 +02002081/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2082 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2083 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2084 * error occurred.
2085 */
2086static 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 +02002087{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002088 struct htx *htx = htxbuf(&check->bi);
2089 struct htx_sl *sl;
2090 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002091 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002092 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002093 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002094 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002095 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002096 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002097
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002099
Christopher Faulet61cc8522020-04-20 14:54:42 +02002100 if (htx->flags & HTX_FL_PARSING_ERROR) {
2101 status = HCHK_STATUS_L7RSP;
2102 goto error;
2103 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002104
Christopher Faulet61cc8522020-04-20 14:54:42 +02002105 if (htx_is_empty(htx)) {
2106 if (last_read) {
2107 status = HCHK_STATUS_L7RSP;
2108 goto error;
2109 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002110 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002112
Christopher Faulet61cc8522020-04-20 14:54:42 +02002113 sl = http_get_stline(htx);
2114 check->code = sl->info.res.status;
2115
2116 if (check->server &&
2117 (check->server->proxy->options & PR_O_DISABLE404) &&
2118 (check->server->next_state != SRV_ST_STOPPED) &&
2119 (check->code == 404)) {
2120 /* 404 may be accepted as "stopping" only if the server was up */
2121 goto out;
2122 }
2123
2124 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2125 /* Make GCC happy ; initialize match to a failure state. */
2126 match = inverse;
2127
2128 switch (expect->type) {
2129 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002130 match = 0;
2131 for (i = 0; i < expect->codes.num; i++) {
2132 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2133 sl->info.res.status <= expect->codes.codes[i][1]) {
2134 match = 1;
2135 break;
2136 }
2137 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002138
2139 /* Set status and description in case of error */
2140 status = HCHK_STATUS_L7STS;
2141 desc = htx_sl_res_reason(sl);
2142 break;
2143 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2144 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2145
2146 /* Set status and description in case of error */
2147 status = HCHK_STATUS_L7STS;
2148 desc = htx_sl_res_reason(sl);
2149 break;
2150
2151 case TCPCHK_EXPECT_HTTP_BODY:
2152 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2153 chunk_reset(&trash);
2154 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2155 enum htx_blk_type type = htx_get_blk_type(blk);
2156
2157 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2158 break;
2159 if (type == HTX_BLK_DATA) {
2160 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2161 break;
2162 }
2163 }
2164
2165 if (!b_data(&trash)) {
2166 if (!last_read)
2167 goto wait_more_data;
2168 status = HCHK_STATUS_L7RSP;
2169 desc = ist("HTTP content check could not find a response body");
2170 goto error;
2171 }
2172
2173 if (!last_read &&
2174 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2175 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2176 ret = TCPCHK_EVAL_WAIT;
2177 goto out;
2178 }
2179
2180 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002181 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002182 else
2183 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2184
2185 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002186 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002187 desc = (inverse
2188 ? ist("HTTP check matched unwanted content")
2189 : ist("HTTP content check did not match"));
2190 break;
2191
2192 default:
2193 /* should never happen */
2194 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002195 goto error;
2196 }
2197
Christopher Faulet61cc8522020-04-20 14:54:42 +02002198 /* Wait for more data on mismatch only if no minimum is defined (-1),
2199 * otherwise the absence of match is already conclusive.
2200 */
2201 if (!match && !last_read && (expect->min_recv == -1)) {
2202 ret = TCPCHK_EVAL_WAIT;
2203 goto out;
2204 }
2205
2206 if (!(match ^ inverse))
2207 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002208
2209 out:
2210 free_trash_chunk(msg);
2211 return ret;
2212
2213 error:
2214 ret = TCPCHK_EVAL_STOP;
2215 msg = alloc_trash_chunk();
2216 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002217 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002218 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2219 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002220
2221 wait_more_data:
2222 ret = TCPCHK_EVAL_WAIT;
2223 goto out;
2224}
2225
Christopher Faulet61cc8522020-04-20 14:54:42 +02002226/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2227 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2228 * if an error occurred.
2229 */
2230static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2231{
2232 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2233 struct tcpcheck_expect *expect = &rule->expect;
2234 struct buffer *msg = NULL;
2235 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002236
Christopher Faulet61cc8522020-04-20 14:54:42 +02002237 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002238
Christopher Faulet61cc8522020-04-20 14:54:42 +02002239 /* The current expect might need more data than the previous one, check again
2240 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002241 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002242 if (!last_read) {
2243 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2244 (b_data(&check->bi) < istlen(expect->data))) {
2245 ret = TCPCHK_EVAL_WAIT;
2246 goto out;
2247 }
2248 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2249 ret = TCPCHK_EVAL_WAIT;
2250 goto out;
2251 }
2252 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002253
Christopher Faulet61cc8522020-04-20 14:54:42 +02002254 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2255 /* Make GCC happy ; initialize match to a failure state. */
2256 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002257
Christopher Faulet61cc8522020-04-20 14:54:42 +02002258 switch (expect->type) {
2259 case TCPCHK_EXPECT_STRING:
2260 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002261 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 +02002262 break;
2263 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002264 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 +02002265 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002266
Christopher Faulet61cc8522020-04-20 14:54:42 +02002267 case TCPCHK_EXPECT_REGEX_BINARY:
2268 chunk_reset(&trash);
2269 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002270 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002271 break;
2272 case TCPCHK_EXPECT_CUSTOM:
2273 if (expect->custom)
2274 ret = expect->custom(check, rule, last_read);
2275 goto out;
2276 default:
2277 /* Should never happen. */
2278 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002279 goto out;
2280 }
2281
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002282
Christopher Faulet61cc8522020-04-20 14:54:42 +02002283 /* Wait for more data on mismatch only if no minimum is defined (-1),
2284 * otherwise the absence of match is already conclusive.
2285 */
2286 if (!match && !last_read && (expect->min_recv == -1)) {
2287 ret = TCPCHK_EVAL_WAIT;
2288 goto out;
2289 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002290
Christopher Faulet61cc8522020-04-20 14:54:42 +02002291 /* Result as expected, next rule. */
2292 if (match ^ inverse)
2293 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002294
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002295
Christopher Faulet61cc8522020-04-20 14:54:42 +02002296 /* From this point on, we matched something we did not want, this is an error state. */
2297 ret = TCPCHK_EVAL_STOP;
2298 msg = alloc_trash_chunk();
2299 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002300 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002301 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2302 free_trash_chunk(msg);
2303 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002304
Christopher Faulet61cc8522020-04-20 14:54:42 +02002305 out:
2306 return ret;
2307}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002308
Christopher Faulet61cc8522020-04-20 14:54:42 +02002309/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2310 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2311 * waits.
2312 */
2313static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2314{
2315 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2316 struct act_rule *act_rule;
2317 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002318
Christopher Faulet61cc8522020-04-20 14:54:42 +02002319 act_rule =rule->action_kw.rule;
2320 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2321 if (act_ret != ACT_RET_CONT) {
2322 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2323 tcpcheck_get_step_id(check, rule));
2324 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2325 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002326 }
2327
Christopher Faulet61cc8522020-04-20 14:54:42 +02002328 return ret;
2329}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002330
Christopher Faulet61cc8522020-04-20 14:54:42 +02002331/* Executes a tcp-check ruleset. Note that this is called both from the
2332 * connection's wake() callback and from the check scheduling task. It returns
2333 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2334 * presenting the risk of an fd replacement.
2335 *
2336 * Please do NOT place any return statement in this function and only leave
2337 * via the out_end_tcpcheck label after setting retcode.
2338 */
2339static int tcpcheck_main(struct check *check)
2340{
2341 struct tcpcheck_rule *rule;
2342 struct conn_stream *cs = check->cs;
2343 struct connection *conn = cs_conn(cs);
2344 int must_read = 1, last_read = 0;
2345 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002346
Christopher Faulet61cc8522020-04-20 14:54:42 +02002347 /* here, we know that the check is complete or that it failed */
2348 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002349 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002350
Christopher Faulet61cc8522020-04-20 14:54:42 +02002351 /* 1- check for connection error, if any */
2352 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2353 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002354
Christopher Faulet61cc8522020-04-20 14:54:42 +02002355 /* 2- check if we are waiting for the connection establishment. It only
2356 * happens during TCPCHK_ACT_CONNECT. */
2357 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2358 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2359 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2360 if (rule->action == TCPCHK_ACT_SEND)
2361 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2362 else if (rule->action == TCPCHK_ACT_EXPECT)
2363 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2364 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002365 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002366 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002367
2368 /* 3- check for pending outgoing data. It only happens during
2369 * TCPCHK_ACT_SEND. */
2370 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2371 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002372 ret = conn->mux->snd_buf(cs, &check->bo,
2373 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002374 if (ret <= 0) {
2375 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2376 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002377 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002378 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002379 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2380 goto out;
2381 }
2382 }
2383 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002384 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002385
Christopher Faulet61cc8522020-04-20 14:54:42 +02002386 /* 4- check if a rule must be resume. It happens if check->current_step
2387 * is defined. */
2388 else if (check->current_step)
2389 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002390
Christopher Faulet61cc8522020-04-20 14:54:42 +02002391 /* 5- It is the first evaluation. We must create a session and preset
2392 * tcp-check variables */
2393 else {
2394 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002395
Christopher Faulet61cc8522020-04-20 14:54:42 +02002396 /* First evaluation, create a session */
2397 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2398 if (!check->sess) {
2399 chunk_printf(&trash, "TCPCHK error allocating check session");
2400 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2401 goto out_end_tcpcheck;
2402 }
2403 vars_init(&check->vars, SCOPE_CHECK);
2404 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002405
Christopher Faulet61cc8522020-04-20 14:54:42 +02002406 /* Preset tcp-check variables */
2407 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2408 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002409
Christopher Faulet61cc8522020-04-20 14:54:42 +02002410 memset(&smp, 0, sizeof(smp));
2411 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2412 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002413 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002414 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002415 }
2416
Christopher Faulet61cc8522020-04-20 14:54:42 +02002417 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002418
Christopher Faulet61cc8522020-04-20 14:54:42 +02002419 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2420 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002421
Christopher Faulet61cc8522020-04-20 14:54:42 +02002422 check->code = 0;
2423 switch (rule->action) {
2424 case TCPCHK_ACT_CONNECT:
2425 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002426
Christopher Faulet61cc8522020-04-20 14:54:42 +02002427 /* close but not release yet previous connection */
2428 if (check->cs) {
2429 cs_close(check->cs);
2430 retcode = -1; /* do not reuse the fd in the caller! */
2431 }
2432 eval_ret = tcpcheck_eval_connect(check, rule);
2433 must_read = 1; last_read = 0;
2434 break;
2435 case TCPCHK_ACT_SEND:
2436 check->current_step = rule;
2437 eval_ret = tcpcheck_eval_send(check, rule);
2438 must_read = 1;
2439 break;
2440 case TCPCHK_ACT_EXPECT:
2441 check->current_step = rule;
2442 if (must_read) {
2443 if (check->proxy->timeout.check)
2444 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002445
Christopher Faulet61cc8522020-04-20 14:54:42 +02002446 eval_ret = tcpcheck_eval_recv(check, rule);
2447 if (eval_ret == TCPCHK_EVAL_STOP)
2448 goto out_end_tcpcheck;
2449 else if (eval_ret == TCPCHK_EVAL_WAIT)
2450 goto out;
2451 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2452 must_read = 0;
2453 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002454
Christopher Faulet61cc8522020-04-20 14:54:42 +02002455 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2456 ? tcpcheck_eval_expect_http(check, rule, last_read)
2457 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002458
Christopher Faulet61cc8522020-04-20 14:54:42 +02002459 if (eval_ret == TCPCHK_EVAL_WAIT) {
2460 check->current_step = rule->expect.head;
2461 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2462 }
2463 break;
2464 case TCPCHK_ACT_ACTION_KW:
2465 /* Don't update the current step */
2466 eval_ret = tcpcheck_eval_action_kw(check, rule);
2467 break;
2468 default:
2469 /* Otherwise, just go to the next one and don't update
2470 * the current step
2471 */
2472 eval_ret = TCPCHK_EVAL_CONTINUE;
2473 break;
2474 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002475
Christopher Faulet61cc8522020-04-20 14:54:42 +02002476 switch (eval_ret) {
2477 case TCPCHK_EVAL_CONTINUE:
2478 break;
2479 case TCPCHK_EVAL_WAIT:
2480 goto out;
2481 case TCPCHK_EVAL_STOP:
2482 goto out_end_tcpcheck;
2483 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002484 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002485
Christopher Faulet61cc8522020-04-20 14:54:42 +02002486 /* All rules was evaluated */
2487 if (check->current_step) {
2488 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002489
Christopher Faulet61cc8522020-04-20 14:54:42 +02002490 if (rule->action == TCPCHK_ACT_EXPECT) {
2491 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002492
Christopher Faulet61cc8522020-04-20 14:54:42 +02002493 if (check->server &&
2494 (check->server->proxy->options & PR_O_DISABLE404) &&
2495 (check->server->next_state != SRV_ST_STOPPED) &&
2496 (check->code == 404)) {
2497 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2498 goto out_end_tcpcheck;
2499 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002500
Christopher Faulet61cc8522020-04-20 14:54:42 +02002501 msg = alloc_trash_chunk();
2502 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002503 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002504 set_server_check_status(check, rule->expect.ok_status,
2505 (msg ? b_head(msg) : "(tcp-check)"));
2506 free_trash_chunk(msg);
2507 }
2508 else if (rule->action == TCPCHK_ACT_CONNECT) {
2509 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002510 enum healthcheck_status status = HCHK_STATUS_L4OK;
2511#ifdef USE_OPENSSL
2512 if (conn && ssl_sock_is_ssl(conn))
2513 status = HCHK_STATUS_L6OK;
2514#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002515 set_server_check_status(check, status, msg);
2516 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002517 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002518 else
2519 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002520
Christopher Faulet61cc8522020-04-20 14:54:42 +02002521 out_end_tcpcheck:
2522 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2523 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002524
Christopher Faulet61cc8522020-04-20 14:54:42 +02002525 out:
2526 return retcode;
2527}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002528
Christopher Faulet14cd3162020-04-16 14:50:06 +02002529
Christopher Faulet61cc8522020-04-20 14:54:42 +02002530/**************************************************************************/
2531/************** Health-checks based on an external process ****************/
2532/**************************************************************************/
2533static struct list pid_list = LIST_HEAD_INIT(pid_list);
2534static struct pool_head *pool_head_pid_list;
2535__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002536
Christopher Faulet61cc8522020-04-20 14:54:42 +02002537struct extcheck_env {
2538 char *name; /* environment variable name */
2539 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2540};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002541
Christopher Faulet61cc8522020-04-20 14:54:42 +02002542/* environment variables memory requirement for different types of data */
2543#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2544 * such environment variables are not updatable. */
2545#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2546#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2547#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002548
Christopher Faulet61cc8522020-04-20 14:54:42 +02002549/* external checks environment variables */
2550enum {
2551 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002552
Christopher Faulet61cc8522020-04-20 14:54:42 +02002553 /* Proxy specific environment variables */
2554 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2555 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2556 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2557 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002558
Christopher Faulet61cc8522020-04-20 14:54:42 +02002559 /* Server specific environment variables */
2560 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2561 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2562 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2563 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2564 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2565 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002566
Christopher Faulet61cc8522020-04-20 14:54:42 +02002567 EXTCHK_SIZE
2568};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002569
Christopher Faulet61cc8522020-04-20 14:54:42 +02002570const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2571 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2572 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2573 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2574 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2575 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2576 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2577 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2578 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2579 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2580 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2581 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2582};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002583
Christopher Faulet61cc8522020-04-20 14:54:42 +02002584void block_sigchld(void)
2585{
2586 sigset_t set;
2587 sigemptyset(&set);
2588 sigaddset(&set, SIGCHLD);
2589 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2590}
Willy Tarreaube373152018-09-06 11:45:30 +02002591
Christopher Faulet61cc8522020-04-20 14:54:42 +02002592void unblock_sigchld(void)
2593{
2594 sigset_t set;
2595 sigemptyset(&set);
2596 sigaddset(&set, SIGCHLD);
2597 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002598}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002599
Christopher Faulet61cc8522020-04-20 14:54:42 +02002600static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002601{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002602 struct pid_list *elem;
2603 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002604
Christopher Faulet61cc8522020-04-20 14:54:42 +02002605 elem = pool_alloc(pool_head_pid_list);
2606 if (!elem)
2607 return NULL;
2608 elem->pid = pid;
2609 elem->t = t;
2610 elem->exited = 0;
2611 check->curpid = elem;
2612 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002613
Christopher Faulet61cc8522020-04-20 14:54:42 +02002614 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2615 LIST_ADD(&pid_list, &elem->list);
2616 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002617
Christopher Faulet61cc8522020-04-20 14:54:42 +02002618 return elem;
2619}
Christopher Faulete5870d82020-04-15 11:32:03 +02002620
Christopher Faulet61cc8522020-04-20 14:54:42 +02002621static void pid_list_del(struct pid_list *elem)
2622{
2623 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625 if (!elem)
2626 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002627
Christopher Faulet61cc8522020-04-20 14:54:42 +02002628 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2629 LIST_DEL(&elem->list);
2630 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632 if (!elem->exited)
2633 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002634
Christopher Faulet61cc8522020-04-20 14:54:42 +02002635 check = elem->t->context;
2636 check->curpid = NULL;
2637 pool_free(pool_head_pid_list, elem);
2638}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002639
Christopher Faulet61cc8522020-04-20 14:54:42 +02002640/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2641static void pid_list_expire(pid_t pid, int status)
2642{
2643 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002644
Christopher Faulet61cc8522020-04-20 14:54:42 +02002645 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2646 list_for_each_entry(elem, &pid_list, list) {
2647 if (elem->pid == pid) {
2648 elem->t->expire = now_ms;
2649 elem->status = status;
2650 elem->exited = 1;
2651 task_wakeup(elem->t, TASK_WOKEN_IO);
2652 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002653 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002654 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002655 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2656}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002657
Christopher Faulet61cc8522020-04-20 14:54:42 +02002658static void sigchld_handler(struct sig_handler *sh)
2659{
2660 pid_t pid;
2661 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002662
Christopher Faulet61cc8522020-04-20 14:54:42 +02002663 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2664 pid_list_expire(pid, status);
2665}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002666
Christopher Faulet61cc8522020-04-20 14:54:42 +02002667static int init_pid_list(void)
2668{
2669 if (pool_head_pid_list != NULL)
2670 /* Nothing to do */
2671 return 0;
2672
2673 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2674 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2675 strerror(errno));
2676 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002677 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002678
Christopher Faulet61cc8522020-04-20 14:54:42 +02002679 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2680 if (pool_head_pid_list == NULL) {
2681 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2682 strerror(errno));
2683 return 1;
2684 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002685
Christopher Faulet61cc8522020-04-20 14:54:42 +02002686 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002687}
2688
Christopher Faulet61cc8522020-04-20 14:54:42 +02002689/* helper macro to set an environment variable and jump to a specific label on failure. */
2690#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002691
Christopher Faulet61cc8522020-04-20 14:54:42 +02002692/*
2693 * helper function to allocate enough memory to store an environment variable.
2694 * It will also check that the environment variable is updatable, and silently
2695 * fail if not.
2696 */
2697static int extchk_setenv(struct check *check, int idx, const char *value)
2698{
2699 int len, ret;
2700 char *envname;
2701 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002702
Christopher Faulet61cc8522020-04-20 14:54:42 +02002703 if (idx < 0 || idx >= EXTCHK_SIZE) {
2704 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2705 return 1;
2706 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002707
Christopher Faulet61cc8522020-04-20 14:54:42 +02002708 envname = extcheck_envs[idx].name;
2709 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002710
Christopher Faulet61cc8522020-04-20 14:54:42 +02002711 /* Check if the environment variable is already set, and silently reject
2712 * the update if this one is not updatable. */
2713 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2714 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002715
Christopher Faulet61cc8522020-04-20 14:54:42 +02002716 /* Instead of sending NOT_USED, sending an empty value is preferable */
2717 if (strcmp(value, "NOT_USED") == 0) {
2718 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002719 }
2720
Christopher Faulet61cc8522020-04-20 14:54:42 +02002721 len = strlen(envname) + 1;
2722 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2723 len += strlen(value);
2724 else
2725 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002726
Christopher Faulet61cc8522020-04-20 14:54:42 +02002727 if (!check->envp[idx])
2728 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002729
Christopher Faulet61cc8522020-04-20 14:54:42 +02002730 if (!check->envp[idx]) {
2731 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2732 return 1;
2733 }
2734 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2735 if (ret < 0) {
2736 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2737 return 1;
2738 }
2739 else if (ret > len) {
2740 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2741 return 1;
2742 }
2743 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002744}
2745
Christopher Faulet61cc8522020-04-20 14:54:42 +02002746static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002747{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002748 struct server *s = check->server;
2749 struct proxy *px = s->proxy;
2750 struct listener *listener = NULL, *l;
2751 int i;
2752 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2753 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002754
Christopher Faulet61cc8522020-04-20 14:54:42 +02002755 list_for_each_entry(l, &px->conf.listeners, by_fe)
2756 /* Use the first INET, INET6 or UNIX listener */
2757 if (l->addr.ss_family == AF_INET ||
2758 l->addr.ss_family == AF_INET6 ||
2759 l->addr.ss_family == AF_UNIX) {
2760 listener = l;
2761 break;
2762 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002763
Christopher Faulet61cc8522020-04-20 14:54:42 +02002764 check->curpid = NULL;
2765 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2766 if (!check->envp) {
2767 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2768 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002769 }
2770
Christopher Faulet61cc8522020-04-20 14:54:42 +02002771 check->argv = calloc(6, sizeof(char *));
2772 if (!check->argv) {
2773 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2774 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002775 }
2776
Christopher Faulet61cc8522020-04-20 14:54:42 +02002777 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002778
Christopher Faulet61cc8522020-04-20 14:54:42 +02002779 if (!listener) {
2780 check->argv[1] = strdup("NOT_USED");
2781 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002782 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002783 else if (listener->addr.ss_family == AF_INET ||
2784 listener->addr.ss_family == AF_INET6) {
2785 addr_to_str(&listener->addr, buf, sizeof(buf));
2786 check->argv[1] = strdup(buf);
2787 port_to_str(&listener->addr, buf, sizeof(buf));
2788 check->argv[2] = strdup(buf);
2789 }
2790 else if (listener->addr.ss_family == AF_UNIX) {
2791 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002792
Christopher Faulet61cc8522020-04-20 14:54:42 +02002793 un = (struct sockaddr_un *)&listener->addr;
2794 check->argv[1] = strdup(un->sun_path);
2795 check->argv[2] = strdup("NOT_USED");
2796 }
2797 else {
2798 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2799 goto err;
2800 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002801
Christopher Faulet61cc8522020-04-20 14:54:42 +02002802 if (!check->argv[1] || !check->argv[2]) {
2803 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2804 goto err;
2805 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002806
Christopher Faulet61cc8522020-04-20 14:54:42 +02002807 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2808 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2809 if (!check->argv[3] || !check->argv[4]) {
2810 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2811 goto err;
2812 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002813
Christopher Faulet61cc8522020-04-20 14:54:42 +02002814 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2815 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2816 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002817
Christopher Faulet61cc8522020-04-20 14:54:42 +02002818 for (i = 0; i < 5; i++) {
2819 if (!check->argv[i]) {
2820 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2821 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002822 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002823 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002824
Christopher Faulet61cc8522020-04-20 14:54:42 +02002825 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2826 /* Add proxy environment variables */
2827 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2828 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2829 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2830 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2831 /* Add server environment variables */
2832 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2833 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2834 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2835 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2836 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2837 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002838
Christopher Faulet61cc8522020-04-20 14:54:42 +02002839 /* Ensure that we don't leave any hole in check->envp */
2840 for (i = 0; i < EXTCHK_SIZE; i++)
2841 if (!check->envp[i])
2842 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002843
Christopher Faulet61cc8522020-04-20 14:54:42 +02002844 return 1;
2845err:
2846 if (check->envp) {
2847 for (i = 0; i < EXTCHK_SIZE; i++)
2848 free(check->envp[i]);
2849 free(check->envp);
2850 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002851 }
2852
Christopher Faulet61cc8522020-04-20 14:54:42 +02002853 if (check->argv) {
2854 for (i = 1; i < 5; i++)
2855 free(check->argv[i]);
2856 free(check->argv);
2857 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002858 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002859 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002860}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002861
Christopher Faulet61cc8522020-04-20 14:54:42 +02002862/*
2863 * establish a server health-check that makes use of a process.
2864 *
2865 * It can return one of :
2866 * - SF_ERR_NONE if everything's OK
2867 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2868 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2869 *
2870 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002871 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002872static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002873{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002874 char buf[256];
2875 struct check *check = t->context;
2876 struct server *s = check->server;
2877 struct proxy *px = s->proxy;
2878 int status;
2879 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002880
Christopher Faulet61cc8522020-04-20 14:54:42 +02002881 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002882
Christopher Faulet61cc8522020-04-20 14:54:42 +02002883 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002884
Christopher Faulet61cc8522020-04-20 14:54:42 +02002885 pid = fork();
2886 if (pid < 0) {
2887 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2888 (global.tune.options & GTUNE_INSECURE_FORK) ?
2889 "" : " (likely caused by missing 'insecure-fork-wanted')",
2890 strerror(errno));
2891 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002892 goto out;
2893 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002894 if (pid == 0) {
2895 /* Child */
2896 extern char **environ;
2897 struct rlimit limit;
2898 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002899
Christopher Faulet61cc8522020-04-20 14:54:42 +02002900 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2901 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002902
Christopher Faulet61cc8522020-04-20 14:54:42 +02002903 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002904
Christopher Faulet61cc8522020-04-20 14:54:42 +02002905 /* restore the initial FD limits */
2906 limit.rlim_cur = rlim_fd_cur_at_boot;
2907 limit.rlim_max = rlim_fd_max_at_boot;
2908 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2909 getrlimit(RLIMIT_NOFILE, &limit);
2910 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2911 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2912 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2913 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002914
Christopher Faulet61cc8522020-04-20 14:54:42 +02002915 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002916
Christopher Faulet61cc8522020-04-20 14:54:42 +02002917 /* Update some environment variables and command args: curconn, server addr and server port */
2918 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002919
Christopher Faulet61cc8522020-04-20 14:54:42 +02002920 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2921 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002922
Christopher Faulet61cc8522020-04-20 14:54:42 +02002923 *check->argv[4] = 0;
2924 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2925 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2926 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002927
Christopher Faulet61cc8522020-04-20 14:54:42 +02002928 haproxy_unblock_signals();
2929 execvp(px->check_command, check->argv);
2930 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2931 strerror(errno));
2932 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002933 }
2934
Christopher Faulet61cc8522020-04-20 14:54:42 +02002935 /* Parent */
2936 if (check->result == CHK_RES_UNKNOWN) {
2937 if (pid_list_add(pid, t) != NULL) {
2938 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2939
2940 if (px->timeout.check && px->timeout.connect) {
2941 int t_con = tick_add(now_ms, px->timeout.connect);
2942 t->expire = tick_first(t->expire, t_con);
2943 }
2944 status = SF_ERR_NONE;
2945 goto out;
2946 }
2947 else {
2948 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2949 }
2950 kill(pid, SIGTERM); /* process creation error */
2951 }
2952 else
2953 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2954
2955out:
2956 unblock_sigchld();
2957 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002958}
2959
Christopher Faulet61cc8522020-04-20 14:54:42 +02002960/*
2961 * manages a server health-check that uses an external process. Returns
2962 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002963 *
2964 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002965 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002966 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002967static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002968{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002969 struct check *check = context;
2970 struct server *s = check->server;
2971 int rv;
2972 int ret;
2973 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002974
Christopher Faulet61cc8522020-04-20 14:54:42 +02002975 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2976 if (!(check->state & CHK_ST_INPROGRESS)) {
2977 /* no check currently running */
2978 if (!expired) /* woke up too early */
2979 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002980
Christopher Faulet61cc8522020-04-20 14:54:42 +02002981 /* we don't send any health-checks when the proxy is
2982 * stopped, the server should not be checked or the check
2983 * is disabled.
2984 */
2985 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2986 s->proxy->state == PR_STSTOPPED)
2987 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002988
Christopher Faulet61cc8522020-04-20 14:54:42 +02002989 /* we'll initiate a new check */
2990 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002991
Christopher Faulet61cc8522020-04-20 14:54:42 +02002992 check->state |= CHK_ST_INPROGRESS;
2993
2994 ret = connect_proc_chk(t);
2995 if (ret == SF_ERR_NONE) {
2996 /* the process was forked, we allow up to min(inter,
2997 * timeout.connect) for it to report its status, but
2998 * only when timeout.check is set as it may be to short
2999 * for a full check otherwise.
3000 */
3001 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3002
3003 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3004 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3005 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003006 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003007 task_set_affinity(t, tid_bit);
3008 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003009 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003010
Christopher Faulet61cc8522020-04-20 14:54:42 +02003011 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003012
Christopher Faulet61cc8522020-04-20 14:54:42 +02003013 check->state &= ~CHK_ST_INPROGRESS;
3014 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003015
Christopher Faulet61cc8522020-04-20 14:54:42 +02003016 /* we allow up to min(inter, timeout.connect) for a connection
3017 * to establish but only when timeout.check is set
3018 * as it may be to short for a full check otherwise
3019 */
3020 while (tick_is_expired(t->expire, now_ms)) {
3021 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003022
Christopher Faulet61cc8522020-04-20 14:54:42 +02003023 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3024 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003025
Christopher Faulet61cc8522020-04-20 14:54:42 +02003026 if (s->proxy->timeout.check)
3027 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003028 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003029 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003030 else {
3031 /* there was a test running.
3032 * First, let's check whether there was an uncaught error,
3033 * which can happen on connect timeout or error.
3034 */
3035 if (check->result == CHK_RES_UNKNOWN) {
3036 /* good connection is enough for pure TCP check */
3037 struct pid_list *elem = check->curpid;
3038 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003039
Christopher Faulet61cc8522020-04-20 14:54:42 +02003040 if (elem->exited) {
3041 status = elem->status; /* Save in case the process exits between use below */
3042 if (!WIFEXITED(status))
3043 check->code = -1;
3044 else
3045 check->code = WEXITSTATUS(status);
3046 if (!WIFEXITED(status) || WEXITSTATUS(status))
3047 status = HCHK_STATUS_PROCERR;
3048 else
3049 status = HCHK_STATUS_PROCOK;
3050 } else if (expired) {
3051 status = HCHK_STATUS_PROCTOUT;
3052 ha_warning("kill %d\n", (int)elem->pid);
3053 kill(elem->pid, SIGTERM);
3054 }
3055 set_server_check_status(check, status, NULL);
3056 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003057
Christopher Faulet61cc8522020-04-20 14:54:42 +02003058 if (check->result == CHK_RES_FAILED) {
3059 /* a failure or timeout detected */
3060 check_notify_failure(check);
3061 }
3062 else if (check->result == CHK_RES_CONDPASS) {
3063 /* check is OK but asks for stopping mode */
3064 check_notify_stopping(check);
3065 }
3066 else if (check->result == CHK_RES_PASSED) {
3067 /* a success was detected */
3068 check_notify_success(check);
3069 }
3070 task_set_affinity(t, 1);
3071 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003072
Christopher Faulet61cc8522020-04-20 14:54:42 +02003073 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003074
Christopher Faulet61cc8522020-04-20 14:54:42 +02003075 rv = 0;
3076 if (global.spread_checks > 0) {
3077 rv = srv_getinter(check) * global.spread_checks / 100;
3078 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3079 }
3080 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3081 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003082
Christopher Faulet61cc8522020-04-20 14:54:42 +02003083 reschedule:
3084 while (tick_is_expired(t->expire, now_ms))
3085 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003086
Christopher Faulet61cc8522020-04-20 14:54:42 +02003087 out_unlock:
3088 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3089 return t;
3090}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003091
Baptiste Assmann248f1172018-03-01 21:49:01 +01003092
Christopher Faulet61cc8522020-04-20 14:54:42 +02003093/**************************************************************************/
3094/***************** Health-checks based on connections *********************/
3095/**************************************************************************/
3096/* This function is used only for server health-checks. It handles connection
3097 * status updates including errors. If necessary, it wakes the check task up.
3098 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3099 * connection (eg: reconnect). It relies on tcpcheck_main().
3100 */
3101static int wake_srv_chk(struct conn_stream *cs)
3102{
3103 struct connection *conn = cs->conn;
3104 struct check *check = cs->data;
3105 struct email_alertq *q = container_of(check, typeof(*q), check);
3106 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003107
Christopher Faulet61cc8522020-04-20 14:54:42 +02003108 if (check->server)
3109 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3110 else
3111 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003112
Christopher Faulet61cc8522020-04-20 14:54:42 +02003113 /* we may have to make progress on the TCP checks */
3114 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003115
Christopher Faulet61cc8522020-04-20 14:54:42 +02003116 cs = check->cs;
3117 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003118
Christopher Faulet61cc8522020-04-20 14:54:42 +02003119 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3120 /* We may get error reports bypassing the I/O handlers, typically
3121 * the case when sending a pure TCP check which fails, then the I/O
3122 * handlers above are not called. This is completely handled by the
3123 * main processing task so let's simply wake it up. If we get here,
3124 * we expect errno to still be valid.
3125 */
3126 chk_report_conn_err(check, errno, 0);
3127 task_wakeup(check->task, TASK_WOKEN_IO);
3128 }
3129
3130 if (check->result != CHK_RES_UNKNOWN) {
3131 /* Check complete or aborted. If connection not yet closed do it
3132 * now and wake the check task up to be sure the result is
3133 * handled ASAP. */
3134 conn_sock_drain(conn);
3135 cs_close(cs);
3136 ret = -1;
3137 /* We may have been scheduled to run, and the
3138 * I/O handler expects to have a cs, so remove
3139 * the tasklet
3140 */
3141 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3142 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003143 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003144
3145 if (check->server)
3146 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003147 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003148 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003149
Christopher Faulet61cc8522020-04-20 14:54:42 +02003150 /* if a connection got replaced, we must absolutely prevent the connection
3151 * handler from touching its fd, and perform the FD polling updates ourselves
3152 */
3153 if (ret < 0)
3154 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003155
Christopher Faulet61cc8522020-04-20 14:54:42 +02003156 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003157}
3158
Christopher Faulet61cc8522020-04-20 14:54:42 +02003159/* This function checks if any I/O is wanted, and if so, attempts to do so */
3160static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003161{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003162 struct check *check = ctx;
3163 struct conn_stream *cs = check->cs;
3164 struct email_alertq *q = container_of(check, typeof(*q), check);
3165 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003166
Christopher Faulet61cc8522020-04-20 14:54:42 +02003167 if (!(check->wait_list.events & SUB_RETRY_SEND))
3168 ret = wake_srv_chk(cs);
3169 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3170 if (check->server)
3171 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3172 else
3173 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003174
Christopher Faulet61cc8522020-04-20 14:54:42 +02003175 if (unlikely(check->result == CHK_RES_FAILED)) {
3176 /* collect possible new errors */
3177 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3178 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003179
Christopher Faulet61cc8522020-04-20 14:54:42 +02003180 /* Reset the check buffer... */
3181 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003182
Christopher Faulet61cc8522020-04-20 14:54:42 +02003183 /* Close the connection... We still attempt to nicely close if,
3184 * for instance, SSL needs to send a "close notify." Later, we perform
3185 * a hard close and reset the connection if some data are pending,
3186 * otherwise we end up with many TIME_WAITs and eat all the source port
3187 * range quickly. To avoid sending RSTs all the time, we first try to
3188 * drain pending data.
3189 */
3190 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3191 * connection, to make sure cs_shutw() will not lead to a shutdown()
3192 * that would provoke TIME_WAITs.
3193 */
3194 cs_shutr(cs, CS_SHR_DRAIN);
3195 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003196
Christopher Faulet61cc8522020-04-20 14:54:42 +02003197 /* OK, let's not stay here forever */
3198 if (check->result == CHK_RES_FAILED)
3199 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003200
Christopher Faulet61cc8522020-04-20 14:54:42 +02003201 task_wakeup(t, TASK_WOKEN_IO);
3202 }
3203
3204 if (check->server)
3205 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3206 else
3207 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003208 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003209 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003210}
3211
Christopher Faulet61cc8522020-04-20 14:54:42 +02003212/* manages a server health-check that uses a connection. Returns
3213 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3214 *
3215 * Please do NOT place any return statement in this function and only leave
3216 * via the out_unlock label.
3217 */
3218static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003219{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003220 struct check *check = context;
3221 struct proxy *proxy = check->proxy;
3222 struct conn_stream *cs = check->cs;
3223 struct connection *conn = cs_conn(cs);
3224 int rv;
3225 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003226
Christopher Faulet61cc8522020-04-20 14:54:42 +02003227 if (check->server)
3228 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3229 if (!(check->state & CHK_ST_INPROGRESS)) {
3230 /* no check currently running */
3231 if (!expired) /* woke up too early */
3232 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003233
Christopher Faulet61cc8522020-04-20 14:54:42 +02003234 /* we don't send any health-checks when the proxy is
3235 * stopped, the server should not be checked or the check
3236 * is disabled.
3237 */
3238 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3239 proxy->state == PR_STSTOPPED)
3240 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003241
Christopher Faulet61cc8522020-04-20 14:54:42 +02003242 /* we'll initiate a new check */
3243 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003244
Christopher Faulet61cc8522020-04-20 14:54:42 +02003245 check->state |= CHK_ST_INPROGRESS;
3246 b_reset(&check->bi);
3247 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003248
Christopher Faulet61cc8522020-04-20 14:54:42 +02003249 task_set_affinity(t, tid_bit);
3250 cs = check->cs;
3251 conn = cs_conn(cs);
3252 if (!conn) {
3253 check->current_step = NULL;
3254 tcpcheck_main(check);
3255 goto out_unlock;
3256 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003257
Christopher Faulet61cc8522020-04-20 14:54:42 +02003258 conn->flags |= CO_FL_ERROR;
3259 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003260
Christopher Faulet61cc8522020-04-20 14:54:42 +02003261 /* here, we have seen a synchronous error, no fd was allocated */
3262 task_set_affinity(t, MAX_THREADS_MASK);
3263 if (cs) {
3264 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003265 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003266 /* We may have been scheduled to run, and the
3267 * I/O handler expects to have a cs, so remove
3268 * the tasklet
3269 */
3270 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3271 cs_destroy(cs);
3272 cs = check->cs = NULL;
3273 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003274 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003275
Christopher Faulet61cc8522020-04-20 14:54:42 +02003276 check->state &= ~CHK_ST_INPROGRESS;
3277 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003278
Christopher Faulet61cc8522020-04-20 14:54:42 +02003279 /* we allow up to min(inter, timeout.connect) for a connection
3280 * to establish but only when timeout.check is set
3281 * as it may be to short for a full check otherwise
3282 */
3283 while (tick_is_expired(t->expire, now_ms)) {
3284 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003285
Christopher Faulet61cc8522020-04-20 14:54:42 +02003286 t_con = tick_add(t->expire, proxy->timeout.connect);
3287 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3288 if (proxy->timeout.check)
3289 t->expire = tick_first(t->expire, t_con);
3290 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003291 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003292 else {
3293 /* there was a test running.
3294 * First, let's check whether there was an uncaught error,
3295 * which can happen on connect timeout or error.
3296 */
3297 if (check->result == CHK_RES_UNKNOWN) {
3298 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3299 chk_report_conn_err(check, 0, expired);
3300 }
3301 else
3302 goto out_unlock; /* timeout not reached, wait again */
3303 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003304
Christopher Faulet61cc8522020-04-20 14:54:42 +02003305 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003306
Christopher Faulet61cc8522020-04-20 14:54:42 +02003307 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003308
Christopher Faulet61cc8522020-04-20 14:54:42 +02003309 if (conn && conn->xprt) {
3310 /* The check was aborted and the connection was not yet closed.
3311 * This can happen upon timeout, or when an external event such
3312 * as a failed response coupled with "observe layer7" caused the
3313 * server state to be suddenly changed.
3314 */
3315 conn_sock_drain(conn);
3316 cs_close(cs);
3317 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003318
Christopher Faulet61cc8522020-04-20 14:54:42 +02003319 if (cs) {
3320 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003321 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003322 /* We may have been scheduled to run, and the
3323 * I/O handler expects to have a cs, so remove
3324 * the tasklet
3325 */
3326 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3327 cs_destroy(cs);
3328 cs = check->cs = NULL;
3329 conn = NULL;
3330 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003331
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003332 if (check->sess != NULL) {
3333 vars_prune(&check->vars, check->sess, NULL);
3334 session_free(check->sess);
3335 check->sess = NULL;
3336 }
3337
Christopher Faulet61cc8522020-04-20 14:54:42 +02003338 if (check->server) {
3339 if (check->result == CHK_RES_FAILED) {
3340 /* a failure or timeout detected */
3341 check_notify_failure(check);
3342 }
3343 else if (check->result == CHK_RES_CONDPASS) {
3344 /* check is OK but asks for stopping mode */
3345 check_notify_stopping(check);
3346 }
3347 else if (check->result == CHK_RES_PASSED) {
3348 /* a success was detected */
3349 check_notify_success(check);
3350 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003351 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003352 task_set_affinity(t, MAX_THREADS_MASK);
3353 check->state &= ~CHK_ST_INPROGRESS;
3354
3355 if (check->server) {
3356 rv = 0;
3357 if (global.spread_checks > 0) {
3358 rv = srv_getinter(check) * global.spread_checks / 100;
3359 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3360 }
3361 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003362 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003363 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003364
Christopher Faulet61cc8522020-04-20 14:54:42 +02003365 reschedule:
3366 while (tick_is_expired(t->expire, now_ms))
3367 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3368 out_unlock:
3369 if (check->server)
3370 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3371 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003372}
3373
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003374
Christopher Faulet61cc8522020-04-20 14:54:42 +02003375/**************************************************************************/
3376/******************* Internals to parse tcp-check rules *******************/
3377/**************************************************************************/
3378struct action_kw_list tcp_check_keywords = {
3379 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3380};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003381
Christopher Faulet61cc8522020-04-20 14:54:42 +02003382/* Return the struct action_kw associated to a keyword */
3383static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003384{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003385 return action_lookup(&tcp_check_keywords.list, kw);
3386}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003387
Christopher Faulet61cc8522020-04-20 14:54:42 +02003388static void action_kw_tcp_check_build_list(struct buffer *chk)
3389{
3390 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003391}
3392
Christopher Faulet61cc8522020-04-20 14:54:42 +02003393/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3394 * returned on error.
3395 */
3396static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3397 struct list *rules, struct action_kw *kw,
3398 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003399{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003400 struct tcpcheck_rule *chk = NULL;
3401 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003402
Christopher Faulet61cc8522020-04-20 14:54:42 +02003403 actrule = calloc(1, sizeof(*actrule));
3404 if (!actrule) {
3405 memprintf(errmsg, "out of memory");
3406 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003407 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003408 actrule->kw = kw;
3409 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003410
Christopher Faulet61cc8522020-04-20 14:54:42 +02003411 cur_arg++;
3412 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3413 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3414 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003415 }
3416
Christopher Faulet61cc8522020-04-20 14:54:42 +02003417 chk = calloc(1, sizeof(*chk));
3418 if (!chk) {
3419 memprintf(errmsg, "out of memory");
3420 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003421 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003422 chk->action = TCPCHK_ACT_ACTION_KW;
3423 chk->action_kw.rule = actrule;
3424 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003425
3426 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003427 free(actrule);
3428 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003429}
3430
Christopher Faulet61cc8522020-04-20 14:54:42 +02003431/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3432 * returned on error.
3433 */
3434static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3435 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003436{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003437 struct tcpcheck_rule *chk = NULL;
3438 struct sockaddr_storage *sk = NULL;
3439 char *comment = NULL, *sni = NULL, *alpn = NULL;
3440 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003441 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003442 unsigned short conn_opts = 0;
3443 long port = 0;
3444 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003445
Christopher Faulet61cc8522020-04-20 14:54:42 +02003446 list_for_each_entry(chk, rules, list) {
3447 if (chk->action == TCPCHK_ACT_CONNECT)
3448 break;
3449 if (chk->action == TCPCHK_ACT_COMMENT ||
3450 chk->action == TCPCHK_ACT_ACTION_KW ||
3451 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3452 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003453
Christopher Faulet61cc8522020-04-20 14:54:42 +02003454 memprintf(errmsg, "first step MUST also be a 'connect', "
3455 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3456 "when there is a 'connect' step in the tcp-check ruleset");
3457 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003458 }
3459
Christopher Faulet61cc8522020-04-20 14:54:42 +02003460 cur_arg++;
3461 while (*(args[cur_arg])) {
3462 if (strcmp(args[cur_arg], "default") == 0)
3463 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3464 else if (strcmp(args[cur_arg], "addr") == 0) {
3465 int port1, port2;
3466 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003467
Christopher Faulet61cc8522020-04-20 14:54:42 +02003468 if (!*(args[cur_arg+1])) {
3469 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3470 goto error;
3471 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003472
Christopher Faulet61cc8522020-04-20 14:54:42 +02003473 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3474 if (!sk) {
3475 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3476 goto error;
3477 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003478
Christopher Faulet61cc8522020-04-20 14:54:42 +02003479 proto = protocol_by_family(sk->ss_family);
3480 if (!proto || !proto->connect) {
3481 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3482 args[cur_arg]);
3483 goto error;
3484 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003485
Christopher Faulet61cc8522020-04-20 14:54:42 +02003486 if (port1 != port2) {
3487 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3488 args[cur_arg], args[cur_arg+1]);
3489 goto error;
3490 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003491
Christopher Faulet61cc8522020-04-20 14:54:42 +02003492 cur_arg++;
3493 }
3494 else if (strcmp(args[cur_arg], "port") == 0) {
3495 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003496
Christopher Faulet61cc8522020-04-20 14:54:42 +02003497 if (!*(args[cur_arg+1])) {
3498 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3499 goto error;
3500 }
3501 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003502
Christopher Faulet61cc8522020-04-20 14:54:42 +02003503 port = 0;
3504 release_sample_expr(port_expr);
3505 p = args[cur_arg]; end = p + strlen(p);
3506 port = read_uint(&p, end);
3507 if (p != end) {
3508 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003509
Christopher Faulet61cc8522020-04-20 14:54:42 +02003510 px->conf.args.ctx = ARGC_SRV;
3511 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3512 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003513
Christopher Faulet61cc8522020-04-20 14:54:42 +02003514 if (!port_expr) {
3515 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3516 goto error;
3517 }
3518 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3519 memprintf(errmsg, "error detected while parsing port expression : "
3520 " fetch method '%s' extracts information from '%s', "
3521 "none of which is available here.\n",
3522 args[cur_arg], sample_src_names(port_expr->fetch->use));
3523 goto error;
3524 }
3525 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3526 }
3527 else if (port > 65535 || port < 1) {
3528 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3529 args[cur_arg]);
3530 goto error;
3531 }
3532 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003533 else if (strcmp(args[cur_arg], "proto") == 0) {
3534 if (!*(args[cur_arg+1])) {
3535 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3536 goto error;
3537 }
3538 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3539 if (!mux_proto) {
3540 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3541 goto error;
3542 }
3543 cur_arg++;
3544 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003545 else if (strcmp(args[cur_arg], "comment") == 0) {
3546 if (!*(args[cur_arg+1])) {
3547 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3548 goto error;
3549 }
3550 cur_arg++;
3551 free(comment);
3552 comment = strdup(args[cur_arg]);
3553 if (!comment) {
3554 memprintf(errmsg, "out of memory");
3555 goto error;
3556 }
3557 }
3558 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3559 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3560 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3561 conn_opts |= TCPCHK_OPT_SOCKS4;
3562 else if (strcmp(args[cur_arg], "linger") == 0)
3563 conn_opts |= TCPCHK_OPT_LINGER;
3564#ifdef USE_OPENSSL
3565 else if (strcmp(args[cur_arg], "ssl") == 0) {
3566 px->options |= PR_O_TCPCHK_SSL;
3567 conn_opts |= TCPCHK_OPT_SSL;
3568 }
3569 else if (strcmp(args[cur_arg], "sni") == 0) {
3570 if (!*(args[cur_arg+1])) {
3571 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3572 goto error;
3573 }
3574 cur_arg++;
3575 free(sni);
3576 sni = strdup(args[cur_arg]);
3577 if (!sni) {
3578 memprintf(errmsg, "out of memory");
3579 goto error;
3580 }
3581 }
3582 else if (strcmp(args[cur_arg], "alpn") == 0) {
3583#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3584 free(alpn);
3585 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3586 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3587 goto error;
3588 }
3589 cur_arg++;
3590#else
3591 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003592 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003593#endif
3594 }
3595#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003596
Christopher Faulet61cc8522020-04-20 14:54:42 +02003597 else {
3598 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3599#ifdef USE_OPENSSL
3600 ", 'ssl', 'sni', 'alpn'"
3601#endif /* USE_OPENSSL */
3602 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3603 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003604 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003605 }
3606 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003607 }
3608
Christopher Faulet61cc8522020-04-20 14:54:42 +02003609 chk = calloc(1, sizeof(*chk));
3610 if (!chk) {
3611 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003612 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003613 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003614 chk->action = TCPCHK_ACT_CONNECT;
3615 chk->comment = comment;
3616 chk->connect.port = port;
3617 chk->connect.options = conn_opts;
3618 chk->connect.sni = sni;
3619 chk->connect.alpn = alpn;
3620 chk->connect.alpn_len= alpn_len;
3621 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003622 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003623 if (sk)
3624 chk->connect.addr = *sk;
3625 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003626
Christopher Faulet61cc8522020-04-20 14:54:42 +02003627 error:
3628 free(alpn);
3629 free(sni);
3630 free(comment);
3631 release_sample_expr(port_expr);
3632 return NULL;
3633}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003634
Christopher Faulet61cc8522020-04-20 14:54:42 +02003635/* Parses and creates a tcp-check send rule. NULL is returned on error */
3636static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3637 const char *file, int line, char **errmsg)
3638{
3639 struct tcpcheck_rule *chk = NULL;
3640 char *comment = NULL, *data = NULL;
3641 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003642
Christopher Faulet61cc8522020-04-20 14:54:42 +02003643 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3644 if (!*(args[cur_arg+1])) {
3645 memprintf(errmsg, "'%s' expects a %s as argument",
3646 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003647 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003648 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003649
Christopher Faulet61cc8522020-04-20 14:54:42 +02003650 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651
Christopher Faulet61cc8522020-04-20 14:54:42 +02003652 cur_arg += 2;
3653 while (*(args[cur_arg])) {
3654 if (strcmp(args[cur_arg], "comment") == 0) {
3655 if (!*(args[cur_arg+1])) {
3656 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3657 goto error;
3658 }
3659 cur_arg++;
3660 free(comment);
3661 comment = strdup(args[cur_arg]);
3662 if (!comment) {
3663 memprintf(errmsg, "out of memory");
3664 goto error;
3665 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003666 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003667 else if (strcmp(args[cur_arg], "log-format") == 0) {
3668 if (type == TCPCHK_SEND_BINARY)
3669 type = TCPCHK_SEND_BINARY_LF;
3670 else if (type == TCPCHK_SEND_STRING)
3671 type = TCPCHK_SEND_STRING_LF;
3672 }
3673 else {
3674 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3675 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003676 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003677 }
3678 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003679 }
3680
Christopher Faulet61cc8522020-04-20 14:54:42 +02003681 chk = calloc(1, sizeof(*chk));
3682 if (!chk) {
3683 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003684 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003685 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003686 chk->action = TCPCHK_ACT_SEND;
3687 chk->comment = comment;
3688 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003689
Christopher Faulet61cc8522020-04-20 14:54:42 +02003690 switch (chk->send.type) {
3691 case TCPCHK_SEND_STRING:
3692 chk->send.data = ist2(strdup(data), strlen(data));
3693 if (!isttest(chk->send.data)) {
3694 memprintf(errmsg, "out of memory");
3695 goto error;
3696 }
3697 break;
3698 case TCPCHK_SEND_BINARY:
3699 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3700 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3701 goto error;
3702 }
3703 break;
3704 case TCPCHK_SEND_STRING_LF:
3705 case TCPCHK_SEND_BINARY_LF:
3706 LIST_INIT(&chk->send.fmt);
3707 px->conf.args.ctx = ARGC_SRV;
3708 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3709 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3710 goto error;
3711 }
3712 break;
3713 case TCPCHK_SEND_HTTP:
3714 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003715 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003716 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003717
Christopher Faulet61cc8522020-04-20 14:54:42 +02003718 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003719
Christopher Faulet61cc8522020-04-20 14:54:42 +02003720 error:
3721 free(chk);
3722 free(comment);
3723 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003724}
3725
Christopher Faulet61cc8522020-04-20 14:54:42 +02003726/* Parses and creates a http-check send rule. NULL is returned on error */
3727static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3728 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003730 struct tcpcheck_rule *chk = NULL;
3731 struct tcpcheck_http_hdr *hdr = NULL;
3732 struct http_hdr hdrs[global.tune.max_http_hdr];
3733 char *meth = NULL, *uri = NULL, *vsn = NULL;
3734 char *body = NULL, *comment = NULL;
3735 unsigned int flags = 0;
3736 int i = 0;
3737
3738 cur_arg++;
3739 while (*(args[cur_arg])) {
3740 if (strcmp(args[cur_arg], "meth") == 0) {
3741 if (!*(args[cur_arg+1])) {
3742 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3743 goto error;
3744 }
3745 cur_arg++;
3746 meth = args[cur_arg];
3747 }
3748 else if (strcmp(args[cur_arg], "uri") == 0) {
3749 if (!*(args[cur_arg+1])) {
3750 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3751 goto error;
3752 }
3753 cur_arg++;
3754 uri = args[cur_arg];
3755 // TODO: log-format uri
3756 }
Christopher Faulet907701b2020-04-28 09:37:00 +02003757 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003758 if (!*(args[cur_arg+1])) {
3759 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3760 goto error;
3761 }
3762 cur_arg++;
3763 vsn = args[cur_arg];
3764 }
3765 else if (strcmp(args[cur_arg], "hdr") == 0) {
3766 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3767 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3768 goto error;
3769 }
3770 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3771 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3772 i++;
3773 cur_arg += 2;
3774 }
3775 else if (strcmp(args[cur_arg], "body") == 0) {
3776 if (!*(args[cur_arg+1])) {
3777 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3778 goto error;
3779 }
3780 cur_arg++;
3781 body = args[cur_arg];
3782 // TODO: log-format body
3783 }
3784 else if (strcmp(args[cur_arg], "comment") == 0) {
3785 if (!*(args[cur_arg+1])) {
3786 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3787 goto error;
3788 }
3789 cur_arg++;
3790 free(comment);
3791 comment = strdup(args[cur_arg]);
3792 if (!comment) {
3793 memprintf(errmsg, "out of memory");
3794 goto error;
3795 }
3796 }
3797 else {
Christopher Faulet907701b2020-04-28 09:37:00 +02003798 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'ver', 'hdr' and 'body' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003799 args[cur_arg]);
3800 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003801 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003802 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003803 }
3804
Christopher Faulet61cc8522020-04-20 14:54:42 +02003805 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003806
Christopher Faulet61cc8522020-04-20 14:54:42 +02003807 chk = calloc(1, sizeof(*chk));
3808 if (!chk) {
3809 memprintf(errmsg, "out of memory");
3810 goto error;
3811 }
3812 chk->action = TCPCHK_ACT_SEND;
3813 chk->comment = comment; comment = NULL;
3814 chk->send.type = TCPCHK_SEND_HTTP;
3815 chk->send.http.flags = flags;
3816 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003817
Christopher Faulet61cc8522020-04-20 14:54:42 +02003818 if (meth) {
3819 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3820 chk->send.http.meth.str.area = strdup(meth);
3821 chk->send.http.meth.str.data = strlen(meth);
3822 if (!chk->send.http.meth.str.area) {
3823 memprintf(errmsg, "out of memory");
3824 goto error;
3825 }
3826 }
3827 if (uri) {
3828 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3829 if (!isttest(chk->send.http.uri)) {
3830 memprintf(errmsg, "out of memory");
3831 goto error;
3832 }
3833 }
3834 if (vsn) {
3835 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3836 if (!isttest(chk->send.http.vsn)) {
3837 memprintf(errmsg, "out of memory");
3838 goto error;
3839 }
3840 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003841 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003842 hdr = calloc(1, sizeof(*hdr));
3843 if (!hdr) {
3844 memprintf(errmsg, "out of memory");
3845 goto error;
3846 }
3847 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003848 hdr->name = istdup(hdrs[i].n);
3849 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003850 memprintf(errmsg, "out of memory");
3851 goto error;
3852 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003853
Christopher Fauletb61caf42020-04-21 10:57:42 +02003854 ist0(hdrs[i].v);
3855 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 +02003856 goto error;
3857 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3858 hdr = NULL;
3859 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003860
Christopher Faulet61cc8522020-04-20 14:54:42 +02003861 if (body) {
3862 chk->send.http.body = ist2(strdup(body), strlen(body));
3863 if (!isttest(chk->send.http.body)) {
3864 memprintf(errmsg, "out of memory");
3865 goto error;
3866 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003867 }
3868
Christopher Faulet61cc8522020-04-20 14:54:42 +02003869 return chk;
3870
3871 error:
3872 free_tcpcheck_http_hdr(hdr);
3873 free_tcpcheck(chk, 0);
3874 free(comment);
3875 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003876}
3877
Christopher Faulet61cc8522020-04-20 14:54:42 +02003878/* Parses and creates a http-check comment rule. NULL is returned on error */
3879static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3880 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003881{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003882 struct tcpcheck_rule *chk = NULL;
3883 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003884
Christopher Faulet61cc8522020-04-20 14:54:42 +02003885 if (!*(args[cur_arg+1])) {
3886 memprintf(errmsg, "expects a string as argument");
3887 goto error;
3888 }
3889 cur_arg++;
3890 comment = strdup(args[cur_arg]);
3891 if (!comment) {
3892 memprintf(errmsg, "out of memory");
3893 goto error;
3894 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003895
Christopher Faulet61cc8522020-04-20 14:54:42 +02003896 chk = calloc(1, sizeof(*chk));
3897 if (!chk) {
3898 memprintf(errmsg, "out of memory");
3899 goto error;
3900 }
3901 chk->action = TCPCHK_ACT_COMMENT;
3902 chk->comment = comment;
3903 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003904
Christopher Faulet61cc8522020-04-20 14:54:42 +02003905 error:
3906 free(comment);
3907 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003908}
3909
Christopher Faulet61cc8522020-04-20 14:54:42 +02003910/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3911 * on error. <proto> is set to the right protocol flags (covered by the
3912 * TCPCHK_RULES_PROTO_CHK mask).
3913 */
3914static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3915 struct list *rules, unsigned int proto,
3916 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003917{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003918 struct tcpcheck_rule *prev_check, *chk = NULL;
3919 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003920 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003921 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3922 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3923 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3924 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3925 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003926 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003927
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003928 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003929 if (!*(args[cur_arg+1])) {
3930 memprintf(errmsg, "expects at least a matching pattern as arguments");
3931 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003932 }
3933
Christopher Faulet61cc8522020-04-20 14:54:42 +02003934 cur_arg++;
3935 while (*(args[cur_arg])) {
3936 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003937
Christopher Faulet61cc8522020-04-20 14:54:42 +02003938 rescan:
3939 if (strcmp(args[cur_arg], "min-recv") == 0) {
3940 if (in_pattern) {
3941 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3942 goto error;
3943 }
3944 if (!*(args[cur_arg+1])) {
3945 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3946 goto error;
3947 }
3948 /* Use an signed integer here because of chksize */
3949 cur_arg++;
3950 min_recv = atol(args[cur_arg]);
3951 if (min_recv < -1 || min_recv > INT_MAX) {
3952 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3953 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003954 }
3955 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003956 else if (*(args[cur_arg]) == '!') {
3957 in_pattern = 1;
3958 while (*(args[cur_arg]) == '!') {
3959 inverse = !inverse;
3960 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003961 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003962 if (!*(args[cur_arg]))
3963 cur_arg++;
3964 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003965 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003966 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3967 if (type != TCPCHK_EXPECT_UNDEF) {
3968 memprintf(errmsg, "only on pattern expected");
3969 goto error;
3970 }
3971 if (proto != TCPCHK_RULES_HTTP_CHK)
3972 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3973 else
3974 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003975
Christopher Faulet61cc8522020-04-20 14:54:42 +02003976 if (!*(args[cur_arg+1])) {
3977 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3978 goto error;
3979 }
3980 cur_arg++;
3981 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003982 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003983 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3984 if (proto == TCPCHK_RULES_HTTP_CHK)
3985 goto bad_http_kw;
3986 if (type != TCPCHK_EXPECT_UNDEF) {
3987 memprintf(errmsg, "only on pattern expected");
3988 goto error;
3989 }
3990 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003991
Christopher Faulet61cc8522020-04-20 14:54:42 +02003992 if (!*(args[cur_arg+1])) {
3993 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3994 goto error;
3995 }
3996 cur_arg++;
3997 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003998 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003999 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4000 if (proto != TCPCHK_RULES_HTTP_CHK)
4001 goto bad_tcp_kw;
4002 if (type != TCPCHK_EXPECT_UNDEF) {
4003 memprintf(errmsg, "only on pattern expected");
4004 goto error;
4005 }
4006 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004007
Christopher Faulet61cc8522020-04-20 14:54:42 +02004008 if (!*(args[cur_arg+1])) {
4009 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4010 goto error;
4011 }
4012 cur_arg++;
4013 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004014 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004015 else if (strcmp(args[cur_arg], "custom") == 0) {
4016 if (in_pattern) {
4017 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4018 goto error;
4019 }
4020 if (type != TCPCHK_EXPECT_UNDEF) {
4021 memprintf(errmsg, "only on pattern expected");
4022 goto error;
4023 }
4024 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004025 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004026 else if (strcmp(args[cur_arg], "comment") == 0) {
4027 if (in_pattern) {
4028 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4029 goto error;
4030 }
4031 if (!*(args[cur_arg+1])) {
4032 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4033 goto error;
4034 }
4035 cur_arg++;
4036 free(comment);
4037 comment = strdup(args[cur_arg]);
4038 if (!comment) {
4039 memprintf(errmsg, "out of memory");
4040 goto error;
4041 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004042 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004043 else if (strcmp(args[cur_arg], "on-success") == 0) {
4044 if (in_pattern) {
4045 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4046 goto error;
4047 }
4048 if (!*(args[cur_arg+1])) {
4049 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4050 goto error;
4051 }
4052 cur_arg++;
4053 free(on_success_msg);
4054 on_success_msg = strdup(args[cur_arg]);
4055 if (!on_success_msg) {
4056 memprintf(errmsg, "out of memory");
4057 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004058 }
4059 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004060 else if (strcmp(args[cur_arg], "on-error") == 0) {
4061 if (in_pattern) {
4062 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4063 goto error;
4064 }
4065 if (!*(args[cur_arg+1])) {
4066 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4067 goto error;
4068 }
4069 cur_arg++;
4070 free(on_error_msg);
4071 on_error_msg = strdup(args[cur_arg]);
4072 if (!on_error_msg) {
4073 memprintf(errmsg, "out of memory");
4074 goto error;
4075 }
4076 }
4077 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4078 if (in_pattern) {
4079 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4080 goto error;
4081 }
4082 if (!*(args[cur_arg+1])) {
4083 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4084 goto error;
4085 }
4086 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4087 ok_st = HCHK_STATUS_L7OKD;
4088 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4089 ok_st = HCHK_STATUS_L7OKCD;
4090 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4091 ok_st = HCHK_STATUS_L6OK;
4092 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4093 ok_st = HCHK_STATUS_L4OK;
4094 else {
4095 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4096 args[cur_arg], args[cur_arg+1]);
4097 goto error;
4098 }
4099 cur_arg++;
4100 }
4101 else if (strcmp(args[cur_arg], "error-status") == 0) {
4102 if (in_pattern) {
4103 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4104 goto error;
4105 }
4106 if (!*(args[cur_arg+1])) {
4107 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4108 goto error;
4109 }
4110 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4111 err_st = HCHK_STATUS_L7RSP;
4112 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4113 err_st = HCHK_STATUS_L7STS;
4114 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4115 err_st = HCHK_STATUS_L6RSP;
4116 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4117 err_st = HCHK_STATUS_L4CON;
4118 else {
4119 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4120 args[cur_arg], args[cur_arg+1]);
4121 goto error;
4122 }
4123 cur_arg++;
4124 }
4125 else if (strcmp(args[cur_arg], "status-code") == 0) {
4126 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004127
Christopher Faulet61cc8522020-04-20 14:54:42 +02004128 if (in_pattern) {
4129 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4130 goto error;
4131 }
4132 if (!*(args[cur_arg+1])) {
4133 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4134 goto error;
4135 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004136
Christopher Faulet61cc8522020-04-20 14:54:42 +02004137 cur_arg++;
4138 release_sample_expr(status_expr);
4139 px->conf.args.ctx = ARGC_SRV;
4140 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4141 file, line, errmsg, &px->conf.args, NULL);
4142 if (!status_expr) {
4143 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4144 goto error;
4145 }
4146 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4147 memprintf(errmsg, "error detected while parsing status-code expression : "
4148 " fetch method '%s' extracts information from '%s', "
4149 "none of which is available here.\n",
4150 args[cur_arg], sample_src_names(status_expr->fetch->use));
4151 goto error;
4152 }
4153 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4154 }
4155 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4156 if (in_pattern) {
4157 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4158 goto error;
4159 }
4160 if (!*(args[cur_arg+1])) {
4161 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4162 goto error;
4163 }
4164 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4165 tout_st = HCHK_STATUS_L7TOUT;
4166 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4167 tout_st = HCHK_STATUS_L6TOUT;
4168 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4169 tout_st = HCHK_STATUS_L4TOUT;
4170 else {
4171 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4172 args[cur_arg], args[cur_arg+1]);
4173 goto error;
4174 }
4175 cur_arg++;
4176 }
4177 else {
4178 if (proto == TCPCHK_RULES_HTTP_CHK) {
4179 bad_http_kw:
4180 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4181 " or comment but got '%s' as argument.", args[cur_arg]);
4182 }
4183 else {
4184 bad_tcp_kw:
4185 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4186 " or comment but got '%s' as argument.", args[cur_arg]);
4187 }
4188 goto error;
4189 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004190
Christopher Faulet61cc8522020-04-20 14:54:42 +02004191 cur_arg++;
4192 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004193
Christopher Faulet61cc8522020-04-20 14:54:42 +02004194 chk = calloc(1, sizeof(*chk));
4195 if (!chk) {
4196 memprintf(errmsg, "out of memory");
4197 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004198 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004199 chk->action = TCPCHK_ACT_EXPECT;
4200 LIST_INIT(&chk->expect.onerror_fmt);
4201 LIST_INIT(&chk->expect.onsuccess_fmt);
4202 chk->comment = comment; comment = NULL;
4203 chk->expect.type = type;
4204 chk->expect.min_recv = min_recv;
4205 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004206 chk->expect.ok_status = ok_st;
4207 chk->expect.err_status = err_st;
4208 chk->expect.tout_status = tout_st;
4209 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004210
Christopher Faulet61cc8522020-04-20 14:54:42 +02004211 if (on_success_msg) {
4212 px->conf.args.ctx = ARGC_SRV;
4213 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4214 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4215 goto error;
4216 }
4217 free(on_success_msg);
4218 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004219 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004220 if (on_error_msg) {
4221 px->conf.args.ctx = ARGC_SRV;
4222 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4223 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4224 goto error;
4225 }
4226 free(on_error_msg);
4227 on_error_msg = NULL;
4228 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004229
Christopher Faulet61cc8522020-04-20 14:54:42 +02004230 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004231 case TCPCHK_EXPECT_HTTP_STATUS: {
4232 const char *p = pattern;
4233 unsigned int c1,c2;
4234
4235 chk->expect.codes.codes = NULL;
4236 chk->expect.codes.num = 0;
4237 while (1) {
4238 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4239 if (*p == '-') {
4240 p++;
4241 c2 = read_uint(&p, pattern + strlen(pattern));
4242 }
4243 if (c1 > c2) {
4244 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4245 goto error;
4246 }
4247
4248 chk->expect.codes.num++;
4249 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4250 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4251 if (!chk->expect.codes.codes) {
4252 memprintf(errmsg, "out of memory");
4253 goto error;
4254 }
4255 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4256 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4257
4258 if (*p == '\0')
4259 break;
4260 if (*p != ',') {
4261 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4262 goto error;
4263 }
4264 p++;
4265 }
4266 break;
4267 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004268 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004269 case TCPCHK_EXPECT_HTTP_BODY:
4270 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004271 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004272 memprintf(errmsg, "out of memory");
4273 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004274 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004275 break;
4276 case TCPCHK_EXPECT_BINARY:
4277 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4278 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4279 goto error;
4280 }
4281 case TCPCHK_EXPECT_REGEX:
4282 case TCPCHK_EXPECT_REGEX_BINARY:
4283 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4284 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004285 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004286 if (!chk->expect.regex)
4287 goto error;
4288 break;
4289 case TCPCHK_EXPECT_CUSTOM:
4290 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4291 break;
4292 case TCPCHK_EXPECT_UNDEF:
4293 free(chk);
4294 memprintf(errmsg, "pattern not found");
4295 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004296 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004297
Christopher Faulet61cc8522020-04-20 14:54:42 +02004298 /* All tcp-check expect points back to the first inverse expect rule in
4299 * a chain of one or more expect rule, potentially itself.
4300 */
4301 chk->expect.head = chk;
4302 list_for_each_entry_rev(prev_check, rules, list) {
4303 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4304 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4305 chk->expect.head = prev_check;
4306 continue;
4307 }
4308 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4309 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004310 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004311 return chk;
4312
4313 error:
4314 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004315 free(comment);
4316 free(on_success_msg);
4317 free(on_error_msg);
4318 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004319 return NULL;
4320}
4321
Christopher Faulet61cc8522020-04-20 14:54:42 +02004322/* Overwrites fields of the old http send rule with those of the new one. When
4323 * replaced, old values are freed and replaced by the new ones. New values are
4324 * not copied but transferred. At the end <new> should be empty and can be
4325 * safely released. This function never fails.
4326 */
4327static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004328{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004329 struct logformat_node *lf, *lfb;
4330 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004331
Christopher Faulet404f9192020-04-09 23:13:54 +02004332
Christopher Faulet61cc8522020-04-20 14:54:42 +02004333 if (new->send.http.meth.str.area) {
4334 free(old->send.http.meth.str.area);
4335 old->send.http.meth.meth = new->send.http.meth.meth;
4336 old->send.http.meth.str.area = new->send.http.meth.str.area;
4337 old->send.http.meth.str.data = new->send.http.meth.str.data;
4338 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004339 }
4340
Christopher Faulet61cc8522020-04-20 14:54:42 +02004341 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4342 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004343 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004344 else
4345 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4346 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4347 old->send.http.uri = new->send.http.uri;
4348 new->send.http.uri = IST_NULL;
4349 }
4350 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4351 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004352 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004353 else
4354 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4355 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4356 LIST_INIT(&old->send.http.uri_fmt);
4357 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4358 LIST_DEL(&lf->list);
4359 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4360 }
4361 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004362
Christopher Faulet61cc8522020-04-20 14:54:42 +02004363 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004364 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004365 old->send.http.vsn = new->send.http.vsn;
4366 new->send.http.vsn = IST_NULL;
4367 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004368
Christopher Faulet61cc8522020-04-20 14:54:42 +02004369 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4370 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4371 LIST_DEL(&hdr->list);
4372 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004373 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004374
4375 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4376 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004377 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004378 else
4379 free_tcpcheck_fmt(&old->send.http.body_fmt);
4380 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4381 old->send.http.body = new->send.http.body;
4382 new->send.http.body = IST_NULL;
4383 }
4384 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4385 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004386 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004387 else
4388 free_tcpcheck_fmt(&old->send.http.body_fmt);
4389 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4390 LIST_INIT(&old->send.http.body_fmt);
4391 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4392 LIST_DEL(&lf->list);
4393 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4394 }
4395 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004396}
4397
Christopher Faulet61cc8522020-04-20 14:54:42 +02004398/* Internal function used to add an http-check rule in a list during the config
4399 * parsing step. Depending on its type, and the previously inserted rules, a
4400 * specific action may be performed or an error may be reported. This functions
4401 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4402 * message.
4403 */
4404static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004405{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004406 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004407
Christopher Faulet61cc8522020-04-20 14:54:42 +02004408 /* the implicit send rule coming from an "option httpchk" line must be
4409 * merged with the first explici http-check send rule, if
4410 * any. Depdending the declaration order some tests are required.
4411 *
4412 * Some tests is also required for other kinds of http-check rules to be
4413 * sure the ruleset remains valid.
4414 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004415
Christopher Faulet61cc8522020-04-20 14:54:42 +02004416 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4417 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4418 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4419 * following tests are performed :
4420 *
4421 * 1- If there is no such rule or if it is not a send rule, the implicit send
4422 * rule is pushed in front of the ruleset
4423 *
4424 * 2- If it is another implicit send rule, it is replaced with the new one.
4425 *
4426 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4427 * both, overwritting the old send rule (the explicit one) with info of the
4428 * new send rule (the implicit one).
4429 */
4430 r = get_first_tcpcheck_rule(rules);
4431 if (r && r->action == TCPCHK_ACT_CONNECT)
4432 r = get_next_tcpcheck_rule(rules, r);
4433 if (!r || r->action != TCPCHK_ACT_SEND)
4434 LIST_ADD(rules->list, &chk->list);
4435 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4436 LIST_DEL(&r->list);
4437 free_tcpcheck(r, 0);
4438 LIST_ADD(rules->list, &chk->list);
4439 }
4440 else {
4441 tcpcheck_overwrite_send_http_rule(r, chk);
4442 free_tcpcheck(chk, 0);
4443 }
4444 }
4445 else {
4446 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4447 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4448 * with an existing implicit send rule, if any. At the end, if there is no error,
4449 * the rule is appended to the list.
4450 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004451
Christopher Faulet61cc8522020-04-20 14:54:42 +02004452 r = get_last_tcpcheck_rule(rules);
4453 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4454 /* no error */;
4455 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4456 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4457 chk->index+1);
4458 return 0;
4459 }
4460 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4461 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4462 chk->index+1);
4463 return 0;
4464 }
4465 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4466 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4467 chk->index+1);
4468 return 0;
4469 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004470
Christopher Faulet61cc8522020-04-20 14:54:42 +02004471 if (chk->action == TCPCHK_ACT_SEND) {
4472 r = get_first_tcpcheck_rule(rules);
4473 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4474 tcpcheck_overwrite_send_http_rule(r, chk);
4475 free_tcpcheck(chk, 0);
4476 LIST_DEL(&r->list);
4477 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4478 chk = r;
4479 }
4480 }
4481 LIST_ADDQ(rules->list, &chk->list);
4482 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004483 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004484}
4485
Christopher Faulet61cc8522020-04-20 14:54:42 +02004486/**************************************************************************/
4487/************************** Init/deinit checks ****************************/
4488/**************************************************************************/
4489static const char *init_check(struct check *check, int type)
4490{
4491 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004492
Christopher Faulet61cc8522020-04-20 14:54:42 +02004493 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4494 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004495
Christopher Faulet61cc8522020-04-20 14:54:42 +02004496 check->bi.area = calloc(check->bi.size, sizeof(char));
4497 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004498
Christopher Faulet61cc8522020-04-20 14:54:42 +02004499 if (!check->bi.area || !check->bo.area)
4500 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004501
Christopher Faulet61cc8522020-04-20 14:54:42 +02004502 check->wait_list.tasklet = tasklet_new();
4503 if (!check->wait_list.tasklet)
4504 return "out of memory while allocating check tasklet";
4505 check->wait_list.events = 0;
4506 check->wait_list.tasklet->process = event_srv_chk_io;
4507 check->wait_list.tasklet->context = check;
4508 return NULL;
4509}
4510
4511void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004512{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004513 task_destroy(check->task);
4514 if (check->wait_list.tasklet)
4515 tasklet_free(check->wait_list.tasklet);
4516
4517 free(check->bi.area);
4518 free(check->bo.area);
4519 if (check->cs) {
4520 free(check->cs->conn);
4521 check->cs->conn = NULL;
4522 cs_free(check->cs);
4523 check->cs = NULL;
4524 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004525}
4526
Christopher Faulet61cc8522020-04-20 14:54:42 +02004527/* manages a server health-check. Returns the time the task accepts to wait, or
4528 * TIME_ETERNITY for infinity.
4529 */
4530static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004531{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004532 struct check *check = context;
4533
4534 if (check->type == PR_O2_EXT_CHK)
4535 return process_chk_proc(t, context, state);
4536 return process_chk_conn(t, context, state);
4537
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004538}
4539
Christopher Faulet61cc8522020-04-20 14:54:42 +02004540
4541static int start_check_task(struct check *check, int mininter,
4542 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004543{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004544 struct task *t;
4545 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004546
Christopher Faulet61cc8522020-04-20 14:54:42 +02004547 if (check->type == PR_O2_EXT_CHK)
4548 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004549
Christopher Faulet61cc8522020-04-20 14:54:42 +02004550 /* task for the check */
4551 if ((t = task_new(thread_mask)) == NULL) {
4552 ha_alert("Starting [%s:%s] check: out of memory.\n",
4553 check->server->proxy->id, check->server->id);
4554 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004555 }
4556
Christopher Faulet61cc8522020-04-20 14:54:42 +02004557 check->task = t;
4558 t->process = process_chk;
4559 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004560
Christopher Faulet61cc8522020-04-20 14:54:42 +02004561 if (mininter < srv_getinter(check))
4562 mininter = srv_getinter(check);
4563
4564 if (global.max_spread_checks && mininter > global.max_spread_checks)
4565 mininter = global.max_spread_checks;
4566
4567 /* check this every ms */
4568 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4569 check->start = now;
4570 task_queue(t);
4571
4572 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004573}
4574
Christopher Faulet61cc8522020-04-20 14:54:42 +02004575/* updates the server's weight during a warmup stage. Once the final weight is
4576 * reached, the task automatically stops. Note that any server status change
4577 * must have updated s->last_change accordingly.
4578 */
4579static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004580{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004581 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004582
Christopher Faulet61cc8522020-04-20 14:54:42 +02004583 /* by default, plan on stopping the task */
4584 t->expire = TICK_ETERNITY;
4585 if ((s->next_admin & SRV_ADMF_MAINT) ||
4586 (s->next_state != SRV_ST_STARTING))
4587 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004588
Christopher Faulet61cc8522020-04-20 14:54:42 +02004589 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004590
Christopher Faulet61cc8522020-04-20 14:54:42 +02004591 /* recalculate the weights and update the state */
4592 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004593
Christopher Faulet61cc8522020-04-20 14:54:42 +02004594 /* probably that we can refill this server with a bit more connections */
4595 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004596
Christopher Faulet61cc8522020-04-20 14:54:42 +02004597 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004598
Christopher Faulet61cc8522020-04-20 14:54:42 +02004599 /* get back there in 1 second or 1/20th of the slowstart interval,
4600 * whichever is greater, resulting in small 5% steps.
4601 */
4602 if (s->next_state == SRV_ST_STARTING)
4603 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4604 return t;
4605}
4606
4607/*
4608 * Start health-check.
4609 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4610 */
4611static int start_checks()
4612{
4613
4614 struct proxy *px;
4615 struct server *s;
4616 struct task *t;
4617 int nbcheck=0, mininter=0, srvpos=0;
4618
4619 /* 0- init the dummy frontend used to create all checks sessions */
4620 init_new_proxy(&checks_fe);
4621 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4622 checks_fe.mode = PR_MODE_TCP;
4623 checks_fe.maxconn = 0;
4624 checks_fe.conn_retries = CONN_RETRIES;
4625 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4626 checks_fe.timeout.client = TICK_ETERNITY;
4627
4628 /* 1- count the checkers to run simultaneously.
4629 * We also determine the minimum interval among all of those which
4630 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4631 * will be used to spread their start-up date. Those which have
4632 * a shorter interval will start independently and will not dictate
4633 * too short an interval for all others.
4634 */
4635 for (px = proxies_list; px; px = px->next) {
4636 for (s = px->srv; s; s = s->next) {
4637 if (s->slowstart) {
4638 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4639 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4640 return ERR_ALERT | ERR_FATAL;
4641 }
4642 /* We need a warmup task that will be called when the server
4643 * state switches from down to up.
4644 */
4645 s->warmup = t;
4646 t->process = server_warmup;
4647 t->context = s;
4648 /* server can be in this state only because of */
4649 if (s->next_state == SRV_ST_STARTING)
4650 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 +02004651 }
4652
Christopher Faulet61cc8522020-04-20 14:54:42 +02004653 if (s->check.state & CHK_ST_CONFIGURED) {
4654 nbcheck++;
4655 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4656 (!mininter || mininter > srv_getinter(&s->check)))
4657 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004658 }
4659
Christopher Faulet61cc8522020-04-20 14:54:42 +02004660 if (s->agent.state & CHK_ST_CONFIGURED) {
4661 nbcheck++;
4662 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4663 (!mininter || mininter > srv_getinter(&s->agent)))
4664 mininter = srv_getinter(&s->agent);
4665 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004666 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004667 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004668
Christopher Faulet61cc8522020-04-20 14:54:42 +02004669 if (!nbcheck)
4670 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004671
Christopher Faulet61cc8522020-04-20 14:54:42 +02004672 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004673
Christopher Faulet61cc8522020-04-20 14:54:42 +02004674 /*
4675 * 2- start them as far as possible from each others. For this, we will
4676 * start them after their interval set to the min interval divided by
4677 * the number of servers, weighted by the server's position in the list.
4678 */
4679 for (px = proxies_list; px; px = px->next) {
4680 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4681 if (init_pid_list()) {
4682 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4683 return ERR_ALERT | ERR_FATAL;
4684 }
4685 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004686
Christopher Faulet61cc8522020-04-20 14:54:42 +02004687 for (s = px->srv; s; s = s->next) {
4688 /* A task for the main check */
4689 if (s->check.state & CHK_ST_CONFIGURED) {
4690 if (s->check.type == PR_O2_EXT_CHK) {
4691 if (!prepare_external_check(&s->check))
4692 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004693 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004694 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4695 return ERR_ALERT | ERR_FATAL;
4696 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004697 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004698
Christopher Faulet61cc8522020-04-20 14:54:42 +02004699 /* A task for a auxiliary agent check */
4700 if (s->agent.state & CHK_ST_CONFIGURED) {
4701 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4702 return ERR_ALERT | ERR_FATAL;
4703 }
4704 srvpos++;
4705 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004706 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004707 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708 return 0;
4709}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004710
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004711
Christopher Faulet61cc8522020-04-20 14:54:42 +02004712/*
4713 * Return value:
4714 * the port to be used for the health check
4715 * 0 in case no port could be found for the check
4716 */
4717static int srv_check_healthcheck_port(struct check *chk)
4718{
4719 int i = 0;
4720 struct server *srv = NULL;
4721
4722 srv = chk->server;
4723
4724 /* by default, we use the health check port ocnfigured */
4725 if (chk->port > 0)
4726 return chk->port;
4727
4728 /* try to get the port from check_core.addr if check.port not set */
4729 i = get_host_port(&chk->addr);
4730 if (i > 0)
4731 return i;
4732
4733 /* try to get the port from server address */
4734 /* prevent MAPPORTS from working at this point, since checks could
4735 * not be performed in such case (MAPPORTS impose a relative ports
4736 * based on live traffic)
4737 */
4738 if (srv->flags & SRV_F_MAPPORTS)
4739 return 0;
4740
4741 i = srv->svc_port; /* by default */
4742 if (i > 0)
4743 return i;
4744
4745 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004746}
4747
Christopher Faulet61cc8522020-04-20 14:54:42 +02004748/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4749 * if an error occurred.
4750 */
4751static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004752{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004753 const char *err;
4754 struct tcpcheck_rule *r;
4755 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004756
Christopher Faulet61cc8522020-04-20 14:54:42 +02004757 if (!srv->do_check)
4758 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004759
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004760
Christopher Faulet61cc8522020-04-20 14:54:42 +02004761 /* If neither a port nor an addr was specified and no check transport
4762 * layer is forced, then the transport layer used by the checks is the
4763 * same as for the production traffic. Otherwise we use raw_sock by
4764 * default, unless one is specified.
4765 */
4766 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4767 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4768 srv->check.use_ssl = srv->use_ssl;
4769 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004770 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004771 else if (srv->check.use_ssl == 1)
4772 srv->check.xprt = xprt_get(XPRT_SSL);
4773 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004774 }
4775
Christopher Faulet12882cf2020-04-23 15:50:18 +02004776 /* Inherit the mux protocol from the server if not already defined for
4777 * the check
4778 */
4779 if (srv->mux_proto && !srv->check.mux_proto)
4780 srv->check.mux_proto = srv->mux_proto;
4781
Christopher Faulet61cc8522020-04-20 14:54:42 +02004782 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004783
Christopher Faulet61cc8522020-04-20 14:54:42 +02004784 /* We need at least a service port, a check port or the first tcp-check
4785 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4786 */
4787 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4788 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4789 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004790
Christopher Faulet61cc8522020-04-20 14:54:42 +02004791 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4792 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4793 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4794 ret |= ERR_ALERT | ERR_ABORT;
4795 goto out;
4796 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004797
Christopher Faulet61cc8522020-04-20 14:54:42 +02004798 /* search the first action (connect / send / expect) in the list */
4799 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4800 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4801 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4802 "nor tcp_check rule 'connect' with port information.\n",
4803 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4804 ret |= ERR_ALERT | ERR_ABORT;
4805 goto out;
4806 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004807
Christopher Faulet61cc8522020-04-20 14:54:42 +02004808 /* scan the tcp-check ruleset to ensure a port has been configured */
4809 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4810 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4811 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4812 "and a tcp_check rule 'connect' with no port information.\n",
4813 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4814 ret |= ERR_ALERT | ERR_ABORT;
4815 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004816 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004817 }
4818
Christopher Faulet61cc8522020-04-20 14:54:42 +02004819 init:
4820 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4821 struct tcpcheck_ruleset *rs = NULL;
4822 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4823 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004824
Christopher Faulet61cc8522020-04-20 14:54:42 +02004825 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4826 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004827
Christopher Faulet61cc8522020-04-20 14:54:42 +02004828 rs = find_tcpcheck_ruleset("*tcp-check");
4829 if (!rs) {
4830 rs = create_tcpcheck_ruleset("*tcp-check");
4831 if (rs == NULL) {
4832 ha_alert("config: %s '%s': out of memory.\n",
4833 proxy_type_str(srv->proxy), srv->proxy->id);
4834 ret |= ERR_ALERT | ERR_FATAL;
4835 goto out;
4836 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004837 }
4838
Christopher Faulet61cc8522020-04-20 14:54:42 +02004839 free_tcpcheck_vars(&rules->preset_vars);
4840 rules->list = &rs->rules;
4841 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004842 }
4843
Christopher Faulet61cc8522020-04-20 14:54:42 +02004844 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4845 if (err) {
4846 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4847 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4848 ret |= ERR_ALERT | ERR_ABORT;
4849 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004850 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004851 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4852 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004853
Christopher Faulet61cc8522020-04-20 14:54:42 +02004854 out:
4855 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004856}
4857
Christopher Faulet61cc8522020-04-20 14:54:42 +02004858/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4859 * if an error occurred.
4860 */
4861static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004862{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004863 struct tcpcheck_rule *chk;
4864 const char *err;
4865 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004866
Christopher Faulet61cc8522020-04-20 14:54:42 +02004867 if (!srv->do_agent)
4868 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004869
Christopher Faulet61cc8522020-04-20 14:54:42 +02004870 /* If there is no connect rule preceeding all send / expect rules, an
4871 * implicit one is inserted before all others.
4872 */
4873 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4874 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4875 chk = calloc(1, sizeof(*chk));
4876 if (!chk) {
4877 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4878 " to agent-check for server '%s' (out of memory).\n",
4879 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4880 ret |= ERR_ALERT | ERR_FATAL;
4881 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004882 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004883 chk->action = TCPCHK_ACT_CONNECT;
4884 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4885 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004886 }
4887
Christopher Faulete5870d82020-04-15 11:32:03 +02004888
Christopher Faulet61cc8522020-04-20 14:54:42 +02004889 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4890 if (err) {
4891 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4892 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4893 ret |= ERR_ALERT | ERR_ABORT;
4894 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004895 }
4896
Christopher Faulet61cc8522020-04-20 14:54:42 +02004897 if (!srv->agent.inter)
4898 srv->agent.inter = srv->check.inter;
4899
4900 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4901 global.maxsock++;
4902
4903 out:
4904 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004905}
4906
Christopher Faulet61cc8522020-04-20 14:54:42 +02004907/* Check tcp-check health-check configuration for the proxy <px>. */
4908static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004909{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004910 struct tcpcheck_rule *chk, *back;
4911 char *comment = NULL, *errmsg = NULL;
4912 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4913 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004914
Christopher Faulet61cc8522020-04-20 14:54:42 +02004915 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4916 deinit_proxy_tcpcheck(px);
4917 goto out;
4918 }
4919
4920 free(px->check_command);
4921 free(px->check_path);
4922 px->check_command = px->check_path = NULL;
4923
4924 if (!px->tcpcheck_rules.list) {
4925 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4926 ret |= ERR_ALERT | ERR_FATAL;
4927 goto out;
4928 }
4929
4930 /* HTTP ruleset only : */
4931 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4932 struct tcpcheck_rule *next;
4933
4934 /* move remaining implicit send rule from "option httpchk" line to the right place.
4935 * If such rule exists, it must be the first one. In this case, the rule is moved
4936 * after the first connect rule, if any. Otherwise, nothing is done.
4937 */
4938 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4939 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4940 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4941 if (next && next->action == TCPCHK_ACT_CONNECT) {
4942 LIST_DEL(&chk->list);
4943 LIST_ADD(&next->list, &chk->list);
4944 chk->index = next->index;
4945 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004947
4948 /* add implicit expect rule if the last one is a send. It is inherited from previous
4949 * versions where the http expect rule was optional. Now it is possible to chained
4950 * send/expect rules but the last expect may still be implicit.
4951 */
4952 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4953 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004954 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004955 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4956 px->conf.file, px->conf.line, &errmsg);
4957 if (!next) {
4958 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4959 "(%s).\n", px->id, errmsg);
4960 free(errmsg);
4961 ret |= ERR_ALERT | ERR_FATAL;
4962 goto out;
4963 }
4964 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4965 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004966 }
4967 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004968
4969 /* For all ruleset: */
4970
4971 /* If there is no connect rule preceeding all send / expect rules, an
4972 * implicit one is inserted before all others.
4973 */
4974 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4975 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4976 chk = calloc(1, sizeof(*chk));
4977 if (!chk) {
4978 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4979 "(out of memory).\n", px->id);
4980 ret |= ERR_ALERT | ERR_FATAL;
4981 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004982 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004983 chk->action = TCPCHK_ACT_CONNECT;
4984 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4985 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4986 }
4987
4988 /* Remove all comment rules. To do so, when a such rule is found, the
4989 * comment is assigned to the following rule(s).
4990 */
4991 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4992 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4993 free(comment);
4994 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004995 }
4996
Christopher Faulet61cc8522020-04-20 14:54:42 +02004997 prev_action = chk->action;
4998 switch (chk->action) {
4999 case TCPCHK_ACT_COMMENT:
5000 free(comment);
5001 comment = chk->comment;
5002 LIST_DEL(&chk->list);
5003 free(chk);
5004 break;
5005 case TCPCHK_ACT_CONNECT:
5006 if (!chk->comment && comment)
5007 chk->comment = strdup(comment);
5008 /* fall though */
5009 case TCPCHK_ACT_ACTION_KW:
5010 free(comment);
5011 comment = NULL;
5012 break;
5013 case TCPCHK_ACT_SEND:
5014 case TCPCHK_ACT_EXPECT:
5015 if (!chk->comment && comment)
5016 chk->comment = strdup(comment);
5017 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005018 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005019 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005020 free(comment);
5021 comment = NULL;
5022
5023 out:
5024 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005025}
5026
Christopher Faulet61cc8522020-04-20 14:54:42 +02005027void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005028{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005029 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5030 px->tcpcheck_rules.flags = 0;
5031 px->tcpcheck_rules.list = NULL;
5032}
Christopher Faulete5870d82020-04-15 11:32:03 +02005033
Christopher Faulet61cc8522020-04-20 14:54:42 +02005034static void deinit_srv_check(struct server *srv)
5035{
5036 if (srv->check.state & CHK_ST_CONFIGURED)
5037 free_check(&srv->check);
5038 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5039 srv->do_check = 0;
5040}
Christopher Faulete5870d82020-04-15 11:32:03 +02005041
Christopher Faulet61cc8522020-04-20 14:54:42 +02005042
5043static void deinit_srv_agent_check(struct server *srv)
5044{
5045 if (srv->agent.tcpcheck_rules) {
5046 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5047 free(srv->agent.tcpcheck_rules);
5048 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005049 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005050
Christopher Faulet61cc8522020-04-20 14:54:42 +02005051 if (srv->agent.state & CHK_ST_CONFIGURED)
5052 free_check(&srv->agent);
5053
5054 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5055 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005056}
5057
Christopher Faulet61cc8522020-04-20 14:54:42 +02005058static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005059{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005060 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005061 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005062 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005063
Christopher Fauletd7cee712020-04-21 13:45:00 +02005064 node = ebpt_first(&shared_tcpchecks);
5065 while (node) {
5066 next = ebpt_next(node);
5067 ebpt_delete(node);
5068 free(node->key);
5069 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005070 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5071 LIST_DEL(&r->list);
5072 free_tcpcheck(r, 0);
5073 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005074 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005075 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005076 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005077}
Christopher Faulete5870d82020-04-15 11:32:03 +02005078
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005079
Christopher Faulet61cc8522020-04-20 14:54:42 +02005080REGISTER_POST_SERVER_CHECK(init_srv_check);
5081REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5082REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5083REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005084
Christopher Faulet61cc8522020-04-20 14:54:42 +02005085REGISTER_SERVER_DEINIT(deinit_srv_check);
5086REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5087REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5088REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005089
Christopher Faulet61cc8522020-04-20 14:54:42 +02005090/**************************************************************************/
5091/****************************** Email alerts ******************************/
5092/* NOTE: It may be pertinent to use an applet to handle email alerts */
5093/* instead of a tcp-check ruleset */
5094/**************************************************************************/
5095void email_alert_free(struct email_alert *alert)
5096{
5097 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005098
Christopher Faulet61cc8522020-04-20 14:54:42 +02005099 if (!alert)
5100 return;
5101
5102 if (alert->rules.list) {
5103 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5104 LIST_DEL(&rule->list);
5105 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005106 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005107 free_tcpcheck_vars(&alert->rules.preset_vars);
5108 free(alert->rules.list);
5109 alert->rules.list = NULL;
5110 }
5111 pool_free(pool_head_email_alert, alert);
5112}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005113
Christopher Faulet61cc8522020-04-20 14:54:42 +02005114static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5115{
5116 struct check *check = context;
5117 struct email_alertq *q;
5118 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005119
Christopher Faulet61cc8522020-04-20 14:54:42 +02005120 q = container_of(check, typeof(*q), check);
5121
5122 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5123 while (1) {
5124 if (!(check->state & CHK_ST_ENABLED)) {
5125 if (LIST_ISEMPTY(&q->email_alerts)) {
5126 /* All alerts processed, queue the task */
5127 t->expire = TICK_ETERNITY;
5128 task_queue(t);
5129 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005130 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005131
5132 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5133 LIST_DEL(&alert->list);
5134 t->expire = now_ms;
5135 check->tcpcheck_rules = &alert->rules;
5136 check->status = HCHK_STATUS_INI;
5137 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005138 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005139
5140 process_chk(t, context, state);
5141 if (check->state & CHK_ST_INPROGRESS)
5142 break;
5143
5144 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5145 email_alert_free(alert);
5146 check->tcpcheck_rules = NULL;
5147 check->server = NULL;
5148 check->state &= ~CHK_ST_ENABLED;
5149 }
5150 end:
5151 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5152 return t;
5153}
5154
5155/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5156 *
5157 * The function returns 1 in success case, otherwise, it returns 0 and err is
5158 * filled.
5159 */
5160int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5161{
5162 struct mailer *mailer;
5163 struct email_alertq *queues;
5164 const char *err_str;
5165 int i = 0;
5166
5167 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5168 memprintf(err, "out of memory while allocating mailer alerts queues");
5169 goto fail_no_queue;
5170 }
5171
5172 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5173 struct email_alertq *q = &queues[i];
5174 struct check *check = &q->check;
5175 struct task *t;
5176
5177 LIST_INIT(&q->email_alerts);
5178 HA_SPIN_INIT(&q->lock);
5179 check->inter = mls->timeout.mail;
5180 check->rise = DEF_AGENT_RISETIME;
5181 check->proxy = p;
5182 check->fall = DEF_AGENT_FALLTIME;
5183 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5184 memprintf(err, "%s", err_str);
5185 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005186 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005187
5188 check->xprt = mailer->xprt;
5189 check->addr = mailer->addr;
5190 check->port = get_host_port(&mailer->addr);
5191
5192 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5193 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005194 goto error;
5195 }
5196
Christopher Faulet61cc8522020-04-20 14:54:42 +02005197 check->task = t;
5198 t->process = process_email_alert;
5199 t->context = check;
5200
5201 /* check this in one ms */
5202 t->expire = TICK_ETERNITY;
5203 check->start = now;
5204 task_queue(t);
5205 }
5206
5207 mls->users++;
5208 free(p->email_alert.mailers.name);
5209 p->email_alert.mailers.m = mls;
5210 p->email_alert.queues = queues;
5211 return 0;
5212
5213 error:
5214 for (i = 0; i < mls->count; i++) {
5215 struct email_alertq *q = &queues[i];
5216 struct check *check = &q->check;
5217
5218 free_check(check);
5219 }
5220 free(queues);
5221 fail_no_queue:
5222 return 1;
5223}
5224
5225static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5226{
5227 struct tcpcheck_rule *tcpcheck, *prev_check;
5228 struct tcpcheck_expect *expect;
5229
5230 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5231 return 0;
5232 memset(tcpcheck, 0, sizeof(*tcpcheck));
5233 tcpcheck->action = TCPCHK_ACT_EXPECT;
5234
5235 expect = &tcpcheck->expect;
5236 expect->type = TCPCHK_EXPECT_STRING;
5237 LIST_INIT(&expect->onerror_fmt);
5238 LIST_INIT(&expect->onsuccess_fmt);
5239 expect->ok_status = HCHK_STATUS_L7OKD;
5240 expect->err_status = HCHK_STATUS_L7RSP;
5241 expect->tout_status = HCHK_STATUS_L7TOUT;
5242 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005243 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005244 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5245 return 0;
5246 }
5247
5248 /* All tcp-check expect points back to the first inverse expect rule
5249 * in a chain of one or more expect rule, potentially itself.
5250 */
5251 tcpcheck->expect.head = tcpcheck;
5252 list_for_each_entry_rev(prev_check, rules->list, list) {
5253 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5254 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5255 tcpcheck->expect.head = prev_check;
5256 continue;
5257 }
5258 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5259 break;
5260 }
5261 LIST_ADDQ(rules->list, &tcpcheck->list);
5262 return 1;
5263}
5264
5265static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5266{
5267 struct tcpcheck_rule *tcpcheck;
5268 struct tcpcheck_send *send;
5269 const char *in;
5270 char *dst;
5271 int i;
5272
5273 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5274 return 0;
5275 memset(tcpcheck, 0, sizeof(*tcpcheck));
5276 tcpcheck->action = TCPCHK_ACT_SEND;
5277
5278 send = &tcpcheck->send;
5279 send->type = TCPCHK_SEND_STRING;
5280
5281 for (i = 0; strs[i]; i++)
5282 send->data.len += strlen(strs[i]);
5283
Christopher Fauletb61caf42020-04-21 10:57:42 +02005284 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005285 if (!isttest(send->data)) {
5286 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5287 return 0;
5288 }
5289
Christopher Fauletb61caf42020-04-21 10:57:42 +02005290 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005291 for (i = 0; strs[i]; i++)
5292 for (in = strs[i]; (*dst = *in++); dst++);
5293 *dst = 0;
5294
5295 LIST_ADDQ(rules->list, &tcpcheck->list);
5296 return 1;
5297}
5298
5299static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5300 struct email_alertq *q, const char *msg)
5301{
5302 struct email_alert *alert;
5303 struct tcpcheck_rule *tcpcheck;
5304 struct check *check = &q->check;
5305
5306 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5307 goto error;
5308 LIST_INIT(&alert->list);
5309 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5310 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5311 if (!alert->rules.list)
5312 goto error;
5313 LIST_INIT(alert->rules.list);
5314 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5315 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005316
Christopher Faulet61cc8522020-04-20 14:54:42 +02005317 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5318 goto error;
5319 memset(tcpcheck, 0, sizeof(*tcpcheck));
5320 tcpcheck->action = TCPCHK_ACT_CONNECT;
5321 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005322
Christopher Faulet61cc8522020-04-20 14:54:42 +02005323 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005324
Christopher Faulet61cc8522020-04-20 14:54:42 +02005325 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005326 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005327
5328 {
5329 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5330 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5331 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005332 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005333
Christopher Faulet61cc8522020-04-20 14:54:42 +02005334 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5335 goto error;
5336
5337 {
5338 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5339 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005340 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005341 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005342
5343 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5344 goto error;
5345
5346 {
5347 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5348 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005349 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005350 }
5351
Christopher Faulet61cc8522020-04-20 14:54:42 +02005352 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5353 goto error;
5354
5355 {
5356 const char * const strs[2] = { "DATA\r\n" };
5357 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005358 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005359 }
5360
5361 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5362 goto error;
5363
5364 {
5365 struct tm tm;
5366 char datestr[48];
5367 const char * const strs[18] = {
5368 "From: ", p->email_alert.from, "\r\n",
5369 "To: ", p->email_alert.to, "\r\n",
5370 "Date: ", datestr, "\r\n",
5371 "Subject: [HAproxy Alert] ", msg, "\r\n",
5372 "\r\n",
5373 msg, "\r\n",
5374 "\r\n",
5375 ".\r\n",
5376 NULL
5377 };
5378
5379 get_localtime(date.tv_sec, &tm);
5380
5381 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005382 goto error;
5383 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005384
5385 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005386 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005387 }
5388
5389 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005390 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005391
5392 {
5393 const char * const strs[2] = { "QUIT\r\n" };
5394 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5395 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005396 }
5397
Christopher Faulet61cc8522020-04-20 14:54:42 +02005398 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5399 goto error;
5400
5401 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5402 task_wakeup(check->task, TASK_WOKEN_MSG);
5403 LIST_ADDQ(&q->email_alerts, &alert->list);
5404 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5405 return 1;
5406
5407error:
5408 email_alert_free(alert);
5409 return 0;
5410}
5411
5412static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5413{
5414 int i;
5415 struct mailer *mailer;
5416
5417 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5418 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5419 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5420 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5421 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005422 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005423 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005424
Christopher Faulet61cc8522020-04-20 14:54:42 +02005425 return;
5426}
5427
5428/*
5429 * Send email alert if configured.
5430 */
5431void send_email_alert(struct server *s, int level, const char *format, ...)
5432{
5433 va_list argp;
5434 char buf[1024];
5435 int len;
5436 struct proxy *p = s->proxy;
5437
5438 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5439 return;
5440
5441 va_start(argp, format);
5442 len = vsnprintf(buf, sizeof(buf), format, argp);
5443 va_end(argp);
5444
5445 if (len < 0 || len >= sizeof(buf)) {
5446 ha_alert("Email alert [%s] could not format message\n", p->id);
5447 return;
5448 }
5449
5450 enqueue_email_alert(p, s, buf);
5451}
5452
5453/**************************************************************************/
5454/************************** Check sample fetches **************************/
5455/**************************************************************************/
5456/* extracts check payload at a fixed position and length */
5457static int
5458smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5459{
5460 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5461 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5462 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5463 struct buffer *buf;
5464
5465 if (!check)
5466 return 0;
5467
5468 buf = &check->bi;
5469 if (buf_offset > b_data(buf))
5470 goto no_match;
5471 if (buf_offset + buf_size > b_data(buf))
5472 buf_size = 0;
5473
5474 /* init chunk as read only */
5475 smp->data.type = SMP_T_STR;
5476 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5477 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5478
5479 return 1;
5480
5481 no_match:
5482 smp->flags = 0;
5483 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005484}
5485
Christopher Faulet61cc8522020-04-20 14:54:42 +02005486static struct sample_fetch_kw_list smp_kws = {ILH, {
5487 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5488 { /* END */ },
5489}};
5490
5491INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5492
5493
5494/**************************************************************************/
5495/************************ Check's parsing functions ***********************/
5496/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005497/* Parses the "tcp-check" proxy keyword */
5498static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5499 struct proxy *defpx, const char *file, int line,
5500 char **errmsg)
5501{
Christopher Faulet404f9192020-04-09 23:13:54 +02005502 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005503 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005504 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005505
5506 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5507 ret = 1;
5508
Christopher Faulet404f9192020-04-09 23:13:54 +02005509 /* Deduce the ruleset name from the proxy info */
5510 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5511 ((curpx == defpx) ? "defaults" : curpx->id),
5512 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005513
Christopher Faulet61cc8522020-04-20 14:54:42 +02005514 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005515 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005516 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005517 if (rs == NULL) {
5518 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005519 goto error;
5520 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005521 }
5522
Gaetan Rivet5301b012020-02-25 17:19:17 +01005523 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005524 if (!LIST_ISEMPTY(&rs->rules)) {
5525 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005526 index = chk->index + 1;
5527 }
5528
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005529 cur_arg = 1;
5530 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005531 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005532 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005533 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005534 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005535 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005536 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005537 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005538 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005539 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5540
5541 if (!kw) {
5542 action_kw_tcp_check_build_list(&trash);
5543 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5544 "%s%s. but got '%s'",
5545 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5546 goto error;
5547 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005548 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005549 }
5550
5551 if (!chk) {
5552 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5553 goto error;
5554 }
5555 ret = (*errmsg != NULL); /* Handle warning */
5556
5557 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005558 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005559 LIST_ADDQ(&rs->rules, &chk->list);
5560
5561 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005562 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005563 /* Use this ruleset if the proxy already has tcp-check enabled */
5564 curpx->tcpcheck_rules.list = &rs->rules;
5565 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5566 }
5567 else {
5568 /* mark this ruleset as unused for now */
5569 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5570 }
5571
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005572 return ret;
5573
5574 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005575 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005576 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005577 return -1;
5578}
5579
Christopher Faulet51b129f2020-04-09 15:54:18 +02005580/* Parses the "http-check" proxy keyword */
5581static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5582 struct proxy *defpx, const char *file, int line,
5583 char **errmsg)
5584{
Christopher Faulete5870d82020-04-15 11:32:03 +02005585 struct tcpcheck_ruleset *rs = NULL;
5586 struct tcpcheck_rule *chk = NULL;
5587 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005588
5589 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5590 ret = 1;
5591
5592 cur_arg = 1;
5593 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5594 /* enable a graceful server shutdown on an HTTP 404 response */
5595 curpx->options |= PR_O_DISABLE404;
5596 if (too_many_args(1, args, errmsg, NULL))
5597 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005598 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005599 }
5600 else if (strcmp(args[cur_arg], "send-state") == 0) {
5601 /* enable emission of the apparent state of a server in HTTP checks */
5602 curpx->options2 |= PR_O2_CHK_SNDST;
5603 if (too_many_args(1, args, errmsg, NULL))
5604 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005605 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005606 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005607
Christopher Faulete5870d82020-04-15 11:32:03 +02005608 /* Deduce the ruleset name from the proxy info */
5609 chunk_printf(&trash, "*http-check-%s_%s-%d",
5610 ((curpx == defpx) ? "defaults" : curpx->id),
5611 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005612
Christopher Faulet61cc8522020-04-20 14:54:42 +02005613 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005614 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005615 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005616 if (rs == NULL) {
5617 memprintf(errmsg, "out of memory.\n");
5618 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005619 }
5620 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005621
Christopher Faulete5870d82020-04-15 11:32:03 +02005622 index = 0;
5623 if (!LIST_ISEMPTY(&rs->rules)) {
5624 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5625 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5626 index = chk->index + 1;
5627 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005628
Christopher Faulete5870d82020-04-15 11:32:03 +02005629 if (strcmp(args[cur_arg], "connect") == 0)
5630 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5631 else if (strcmp(args[cur_arg], "send") == 0)
5632 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5633 else if (strcmp(args[cur_arg], "expect") == 0)
5634 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5635 file, line, errmsg);
5636 else if (strcmp(args[cur_arg], "comment") == 0)
5637 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5638 else {
5639 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005640
Christopher Faulete5870d82020-04-15 11:32:03 +02005641 if (!kw) {
5642 action_kw_tcp_check_build_list(&trash);
5643 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5644 " 'send', 'expect'%s%s. but got '%s'",
5645 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5646 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005647 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005648 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5649 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005650
Christopher Faulete5870d82020-04-15 11:32:03 +02005651 if (!chk) {
5652 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5653 goto error;
5654 }
5655 ret = (*errmsg != NULL); /* Handle warning */
5656
5657 chk->index = index;
5658 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5659 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5660 /* Use this ruleset if the proxy already has http-check enabled */
5661 curpx->tcpcheck_rules.list = &rs->rules;
5662 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5663 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5664 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5665 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005666 goto error;
5667 }
5668 }
5669 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005670 /* mark this ruleset as unused for now */
5671 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5672 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005673 }
5674
Christopher Faulete5870d82020-04-15 11:32:03 +02005675 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005676 return ret;
5677
5678 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005679 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005680 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005681 return -1;
5682}
5683
Christopher Faulete9111b62020-04-09 18:12:08 +02005684/* Parses the "external-check" proxy keyword */
5685static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5686 struct proxy *defpx, const char *file, int line,
5687 char **errmsg)
5688{
5689 int cur_arg, ret = 0;
5690
5691 cur_arg = 1;
5692 if (!*(args[cur_arg])) {
5693 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5694 goto error;
5695 }
5696
5697 if (strcmp(args[cur_arg], "command") == 0) {
5698 if (too_many_args(2, args, errmsg, NULL))
5699 goto error;
5700 if (!*(args[cur_arg+1])) {
5701 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5702 goto error;
5703 }
5704 free(curpx->check_command);
5705 curpx->check_command = strdup(args[cur_arg+1]);
5706 }
5707 else if (strcmp(args[cur_arg], "path") == 0) {
5708 if (too_many_args(2, args, errmsg, NULL))
5709 goto error;
5710 if (!*(args[cur_arg+1])) {
5711 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5712 goto error;
5713 }
5714 free(curpx->check_path);
5715 curpx->check_path = strdup(args[cur_arg+1]);
5716 }
5717 else {
5718 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5719 args[0], args[1]);
5720 goto error;
5721 }
5722
5723 ret = (*errmsg != NULL); /* Handle warning */
5724 return ret;
5725
5726error:
5727 return -1;
5728}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005729
Christopher Faulet430e4802020-04-09 15:28:16 +02005730/* Parses the "option tcp-check" proxy keyword */
5731int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5732 const char *file, int line)
5733{
Christopher Faulet404f9192020-04-09 23:13:54 +02005734 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005735 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5736 int err_code = 0;
5737
5738 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5739 err_code |= ERR_WARN;
5740
5741 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5742 goto out;
5743
Christopher Faulet404f9192020-04-09 23:13:54 +02005744 curpx->options2 &= ~PR_O2_CHK_ANY;
5745 curpx->options2 |= PR_O2_TCPCHK_CHK;
5746
Christopher Fauletd7e63962020-04-17 20:15:59 +02005747 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005748 /* If a tcp-check rulesset is already set, do nothing */
5749 if (rules->list)
5750 goto out;
5751
5752 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5753 * get it.
5754 */
5755 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5756 goto curpx_ruleset;
5757
5758 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5759 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005760 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005761 if (rs)
5762 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005763 }
5764
Christopher Faulet404f9192020-04-09 23:13:54 +02005765 curpx_ruleset:
5766 /* Deduce the ruleset name from the proxy info */
5767 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5768 ((curpx == defpx) ? "defaults" : curpx->id),
5769 curpx->conf.file, curpx->conf.line);
5770
Christopher Faulet61cc8522020-04-20 14:54:42 +02005771 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005772 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005773 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005774 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005775 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5776 goto error;
5777 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005778 }
5779
Christopher Faulet404f9192020-04-09 23:13:54 +02005780 ruleset_found:
5781 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005782 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005783 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005784
5785 out:
5786 return err_code;
5787
5788 error:
5789 err_code |= ERR_ALERT | ERR_FATAL;
5790 goto out;
5791}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005792
5793/* Parses the "option redis-check" proxy keyword */
5794int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5795 const char *file, int line)
5796{
5797 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5798 static char *redis_res = "+PONG\r\n";
5799
5800 struct tcpcheck_ruleset *rs = NULL;
5801 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5802 struct tcpcheck_rule *chk;
5803 char *errmsg = NULL;
5804 int err_code = 0;
5805
5806 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5807 err_code |= ERR_WARN;
5808
5809 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5810 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005811
5812 curpx->options2 &= ~PR_O2_CHK_ANY;
5813 curpx->options2 |= PR_O2_TCPCHK_CHK;
5814
5815 free_tcpcheck_vars(&rules->preset_vars);
5816 rules->list = NULL;
5817 rules->flags = 0;
5818
Christopher Faulet61cc8522020-04-20 14:54:42 +02005819 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005820 if (rs)
5821 goto ruleset_found;
5822
Christopher Faulet61cc8522020-04-20 14:54:42 +02005823 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005824 if (rs == NULL) {
5825 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5826 goto error;
5827 }
5828
5829 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5830 1, curpx, &rs->rules, file, line, &errmsg);
5831 if (!chk) {
5832 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5833 goto error;
5834 }
5835 chk->index = 0;
5836 LIST_ADDQ(&rs->rules, &chk->list);
5837
5838 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5839 "error-status", "L7STS",
5840 "on-error", "%[check.payload(),cut_crlf]",
5841 "on-success", "Redis server is ok",
5842 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005843 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005844 if (!chk) {
5845 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5846 goto error;
5847 }
5848 chk->index = 1;
5849 LIST_ADDQ(&rs->rules, &chk->list);
5850
Christopher Fauletd7cee712020-04-21 13:45:00 +02005851 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005852
5853 ruleset_found:
5854 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005855 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005856
5857 out:
5858 free(errmsg);
5859 return err_code;
5860
5861 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005862 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005863 err_code |= ERR_ALERT | ERR_FATAL;
5864 goto out;
5865}
5866
Christopher Faulet811f78c2020-04-01 11:10:27 +02005867
5868/* Parses the "option ssl-hello-chk" proxy keyword */
5869int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5870 const char *file, int line)
5871{
5872 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5873 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5874 *
5875 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5876 */
5877 static char sslv3_client_hello[] = {
5878 "16" /* ContentType : 0x16 = Hanshake */
5879 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5880 "0079" /* ContentLength : 0x79 bytes after this one */
5881 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5882 "000075" /* HandshakeLength : 0x75 bytes after this one */
5883 "0300" /* Hello Version : 0x0300 = v3 */
5884 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5885 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5886 "00" /* Session ID length : empty (no session ID) */
5887 "004E" /* Cipher Suite Length : 78 bytes after this one */
5888 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5889 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5890 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5891 "000D" "000E" "000F" "0010" /* various bit lengths, */
5892 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5893 "0015" "0016" "0017" "0018"
5894 "0019" "001A" "001B" "002F"
5895 "0030" "0031" "0032" "0033"
5896 "0034" "0035" "0036" "0037"
5897 "0038" "0039" "003A"
5898 "01" /* Compression Length : 0x01 = 1 byte for types */
5899 "00" /* Compression Type : 0x00 = NULL compression */
5900 };
5901
5902 struct tcpcheck_ruleset *rs = NULL;
5903 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5904 struct tcpcheck_rule *chk;
5905 char *errmsg = NULL;
5906 int err_code = 0;
5907
5908 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5909 err_code |= ERR_WARN;
5910
5911 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5912 goto out;
5913
Christopher Faulet811f78c2020-04-01 11:10:27 +02005914 curpx->options2 &= ~PR_O2_CHK_ANY;
5915 curpx->options2 |= PR_O2_TCPCHK_CHK;
5916
5917 free_tcpcheck_vars(&rules->preset_vars);
5918 rules->list = NULL;
5919 rules->flags = 0;
5920
Christopher Faulet61cc8522020-04-20 14:54:42 +02005921 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005922 if (rs)
5923 goto ruleset_found;
5924
Christopher Faulet61cc8522020-04-20 14:54:42 +02005925 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005926 if (rs == NULL) {
5927 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5928 goto error;
5929 }
5930
5931 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5932 1, curpx, &rs->rules, file, line, &errmsg);
5933 if (!chk) {
5934 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5935 goto error;
5936 }
5937 chk->index = 0;
5938 LIST_ADDQ(&rs->rules, &chk->list);
5939
5940 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005941 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005942 "error-status", "L6RSP", "tout-status", "L6TOUT",
5943 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005944 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005945 if (!chk) {
5946 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5947 goto error;
5948 }
5949 chk->index = 1;
5950 LIST_ADDQ(&rs->rules, &chk->list);
5951
Christopher Fauletd7cee712020-04-21 13:45:00 +02005952 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005953
5954 ruleset_found:
5955 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005956 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005957
5958 out:
5959 free(errmsg);
5960 return err_code;
5961
5962 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005963 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005964 err_code |= ERR_ALERT | ERR_FATAL;
5965 goto out;
5966}
5967
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005968/* Parses the "option smtpchk" proxy keyword */
5969int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5970 const char *file, int line)
5971{
5972 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5973
5974 struct tcpcheck_ruleset *rs = NULL;
5975 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5976 struct tcpcheck_rule *chk;
5977 struct tcpcheck_var *var = NULL;
5978 char *cmd = NULL, *errmsg = NULL;
5979 int err_code = 0;
5980
5981 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5982 err_code |= ERR_WARN;
5983
5984 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5985 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005986
5987 curpx->options2 &= ~PR_O2_CHK_ANY;
5988 curpx->options2 |= PR_O2_TCPCHK_CHK;
5989
5990 free_tcpcheck_vars(&rules->preset_vars);
5991 rules->list = NULL;
5992 rules->flags = 0;
5993
5994 cur_arg += 2;
5995 if (*args[cur_arg] && *args[cur_arg+1] &&
5996 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5997 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5998 if (cmd)
5999 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6000 }
6001 else {
6002 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6003 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6004 cmd = strdup("HELO localhost");
6005 }
6006
Christopher Fauletb61caf42020-04-21 10:57:42 +02006007 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006008 if (cmd == NULL || var == NULL) {
6009 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6010 goto error;
6011 }
6012 var->data.type = SMP_T_STR;
6013 var->data.u.str.area = cmd;
6014 var->data.u.str.data = strlen(cmd);
6015 LIST_INIT(&var->list);
6016 LIST_ADDQ(&rules->preset_vars, &var->list);
6017 cmd = NULL;
6018 var = NULL;
6019
Christopher Faulet61cc8522020-04-20 14:54:42 +02006020 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006021 if (rs)
6022 goto ruleset_found;
6023
Christopher Faulet61cc8522020-04-20 14:54:42 +02006024 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006025 if (rs == NULL) {
6026 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6027 goto error;
6028 }
6029
6030 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6031 1, curpx, &rs->rules, file, line, &errmsg);
6032 if (!chk) {
6033 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6034 goto error;
6035 }
6036 chk->index = 0;
6037 LIST_ADDQ(&rs->rules, &chk->list);
6038
6039 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6040 "min-recv", "4",
6041 "error-status", "L7RSP",
6042 "on-error", "%[check.payload(),cut_crlf]",
6043 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006044 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006045 if (!chk) {
6046 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6047 goto error;
6048 }
6049 chk->index = 1;
6050 LIST_ADDQ(&rs->rules, &chk->list);
6051
6052 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6053 "min-recv", "4",
6054 "error-status", "L7STS",
6055 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6056 "status-code", "check.payload(0,3)",
6057 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006058 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006059 if (!chk) {
6060 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6061 goto error;
6062 }
6063 chk->index = 2;
6064 LIST_ADDQ(&rs->rules, &chk->list);
6065
6066 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6067 1, curpx, &rs->rules, file, line, &errmsg);
6068 if (!chk) {
6069 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6070 goto error;
6071 }
6072 chk->index = 3;
6073 LIST_ADDQ(&rs->rules, &chk->list);
6074
6075 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6076 "min-recv", "4",
6077 "error-status", "L7STS",
6078 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6079 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6080 "status-code", "check.payload(0,3)",
6081 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006082 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006083 if (!chk) {
6084 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6085 goto error;
6086 }
6087 chk->index = 4;
6088 LIST_ADDQ(&rs->rules, &chk->list);
6089
Christopher Fauletd7cee712020-04-21 13:45:00 +02006090 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006091
6092 ruleset_found:
6093 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006094 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006095
6096 out:
6097 free(errmsg);
6098 return err_code;
6099
6100 error:
6101 free(cmd);
6102 free(var);
6103 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006104 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006105 err_code |= ERR_ALERT | ERR_FATAL;
6106 goto out;
6107}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006108
Christopher Fauletce355072020-04-02 11:44:39 +02006109/* Parses the "option pgsql-check" proxy keyword */
6110int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6111 const char *file, int line)
6112{
6113 static char pgsql_req[] = {
6114 "%[var(check.plen),htonl,hex]" /* The packet length*/
6115 "00030000" /* the version 3.0 */
6116 "7573657200" /* "user" key */
6117 "%[var(check.username),hex]00" /* the username */
6118 "00"
6119 };
6120
6121 struct tcpcheck_ruleset *rs = NULL;
6122 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6123 struct tcpcheck_rule *chk;
6124 struct tcpcheck_var *var = NULL;
6125 char *user = NULL, *errmsg = NULL;
6126 size_t packetlen = 0;
6127 int err_code = 0;
6128
6129 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6130 err_code |= ERR_WARN;
6131
6132 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6133 goto out;
6134
Christopher Fauletce355072020-04-02 11:44:39 +02006135 curpx->options2 &= ~PR_O2_CHK_ANY;
6136 curpx->options2 |= PR_O2_TCPCHK_CHK;
6137
6138 free_tcpcheck_vars(&rules->preset_vars);
6139 rules->list = NULL;
6140 rules->flags = 0;
6141
6142 cur_arg += 2;
6143 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6144 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6145 file, line, args[0], args[1]);
6146 goto error;
6147 }
6148 if (strcmp(args[cur_arg], "user") == 0) {
6149 packetlen = 15 + strlen(args[cur_arg+1]);
6150 user = strdup(args[cur_arg+1]);
6151
Christopher Fauletb61caf42020-04-21 10:57:42 +02006152 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006153 if (user == NULL || var == NULL) {
6154 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6155 goto error;
6156 }
6157 var->data.type = SMP_T_STR;
6158 var->data.u.str.area = user;
6159 var->data.u.str.data = strlen(user);
6160 LIST_INIT(&var->list);
6161 LIST_ADDQ(&rules->preset_vars, &var->list);
6162 user = NULL;
6163 var = NULL;
6164
Christopher Fauletb61caf42020-04-21 10:57:42 +02006165 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006166 if (var == NULL) {
6167 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6168 goto error;
6169 }
6170 var->data.type = SMP_T_SINT;
6171 var->data.u.sint = packetlen;
6172 LIST_INIT(&var->list);
6173 LIST_ADDQ(&rules->preset_vars, &var->list);
6174 var = NULL;
6175 }
6176 else {
6177 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6178 file, line, args[0], args[1]);
6179 goto error;
6180 }
6181
Christopher Faulet61cc8522020-04-20 14:54:42 +02006182 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006183 if (rs)
6184 goto ruleset_found;
6185
Christopher Faulet61cc8522020-04-20 14:54:42 +02006186 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006187 if (rs == NULL) {
6188 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6189 goto error;
6190 }
6191
6192 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6193 1, curpx, &rs->rules, file, line, &errmsg);
6194 if (!chk) {
6195 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6196 goto error;
6197 }
6198 chk->index = 0;
6199 LIST_ADDQ(&rs->rules, &chk->list);
6200
6201 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6202 1, curpx, &rs->rules, file, line, &errmsg);
6203 if (!chk) {
6204 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6205 goto error;
6206 }
6207 chk->index = 1;
6208 LIST_ADDQ(&rs->rules, &chk->list);
6209
6210 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6211 "min-recv", "5",
6212 "error-status", "L7RSP",
6213 "on-error", "%[check.payload(6,0)]",
6214 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006215 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006216 if (!chk) {
6217 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6218 goto error;
6219 }
6220 chk->index = 2;
6221 LIST_ADDQ(&rs->rules, &chk->list);
6222
Christopher Fauletb841c742020-04-27 18:29:49 +02006223 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 +02006224 "min-recv", "9",
6225 "error-status", "L7STS",
6226 "on-success", "PostgreSQL server is ok",
6227 "on-error", "PostgreSQL unknown error",
6228 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006229 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006230 if (!chk) {
6231 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6232 goto error;
6233 }
6234 chk->index = 3;
6235 LIST_ADDQ(&rs->rules, &chk->list);
6236
Christopher Fauletd7cee712020-04-21 13:45:00 +02006237 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006238
6239 ruleset_found:
6240 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006241 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006242
6243 out:
6244 free(errmsg);
6245 return err_code;
6246
6247 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006248 free(user);
6249 free(var);
6250 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006251 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006252 err_code |= ERR_ALERT | ERR_FATAL;
6253 goto out;
6254}
6255
6256
6257/* Parses the "option mysql-check" proxy keyword */
6258int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6259 const char *file, int line)
6260{
6261 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6262 * const char mysql40_client_auth_pkt[] = {
6263 * "\x0e\x00\x00" // packet length
6264 * "\x01" // packet number
6265 * "\x00\x00" // client capabilities
6266 * "\x00\x00\x01" // max packet
6267 * "haproxy\x00" // username (null terminated string)
6268 * "\x00" // filler (always 0x00)
6269 * "\x01\x00\x00" // packet length
6270 * "\x00" // packet number
6271 * "\x01" // COM_QUIT command
6272 * };
6273 */
6274 static char mysql40_rsname[] = "*mysql40-check";
6275 static char mysql40_req[] = {
6276 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6277 "0080" /* client capabilities */
6278 "000001" /* max packet */
6279 "%[var(check.username),hex]00" /* the username */
6280 "00" /* filler (always 0x00) */
6281 "010000" /* packet length*/
6282 "00" /* sequence ID */
6283 "01" /* COM_QUIT command */
6284 };
6285
6286 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6287 * const char mysql41_client_auth_pkt[] = {
6288 * "\x0e\x00\x00\" // packet length
6289 * "\x01" // packet number
6290 * "\x00\x00\x00\x00" // client capabilities
6291 * "\x00\x00\x00\x01" // max packet
6292 * "\x21" // character set (UTF-8)
6293 * char[23] // All zeroes
6294 * "haproxy\x00" // username (null terminated string)
6295 * "\x00" // filler (always 0x00)
6296 * "\x01\x00\x00" // packet length
6297 * "\x00" // packet number
6298 * "\x01" // COM_QUIT command
6299 * };
6300 */
6301 static char mysql41_rsname[] = "*mysql41-check";
6302 static char mysql41_req[] = {
6303 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6304 "00820000" /* client capabilities */
6305 "00800001" /* max packet */
6306 "21" /* character set (UTF-8) */
6307 "000000000000000000000000" /* 23 bytes, al zeroes */
6308 "0000000000000000000000"
6309 "%[var(check.username),hex]00" /* the username */
6310 "00" /* filler (always 0x00) */
6311 "010000" /* packet length*/
6312 "00" /* sequence ID */
6313 "01" /* COM_QUIT command */
6314 };
6315
6316 struct tcpcheck_ruleset *rs = NULL;
6317 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6318 struct tcpcheck_rule *chk;
6319 struct tcpcheck_var *var = NULL;
6320 char *mysql_rsname = "*mysql-check";
6321 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6322 int index = 0, err_code = 0;
6323
6324 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6325 err_code |= ERR_WARN;
6326
6327 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6328 goto out;
6329
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006330 curpx->options2 &= ~PR_O2_CHK_ANY;
6331 curpx->options2 |= PR_O2_TCPCHK_CHK;
6332
6333 free_tcpcheck_vars(&rules->preset_vars);
6334 rules->list = NULL;
6335 rules->flags = 0;
6336
6337 cur_arg += 2;
6338 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006339 int packetlen, userlen;
6340
6341 if (strcmp(args[cur_arg], "user") != 0) {
6342 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6343 file, line, args[0], args[1], args[cur_arg]);
6344 goto error;
6345 }
6346
6347 if (*(args[cur_arg+1]) == 0) {
6348 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6349 file, line, args[0], args[1], args[cur_arg]);
6350 goto error;
6351 }
6352
6353 hdr = calloc(4, sizeof(*hdr));
6354 user = strdup(args[cur_arg+1]);
6355 userlen = strlen(args[cur_arg+1]);
6356
6357 if (hdr == NULL || user == NULL) {
6358 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6359 goto error;
6360 }
6361
6362 if (*args[cur_arg+2]) {
6363 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6364 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6365 file, line, args[cur_arg], args[cur_arg+2]);
6366 goto error;
6367 }
6368 packetlen = userlen + 7 + 27;
6369 mysql_req = mysql41_req;
6370 mysql_rsname = mysql41_rsname;
6371 }
6372 else {
6373 packetlen = userlen + 7;
6374 mysql_req = mysql40_req;
6375 mysql_rsname = mysql40_rsname;
6376 }
6377
6378 hdr[0] = (unsigned char)(packetlen & 0xff);
6379 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6380 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6381 hdr[3] = 1;
6382
Christopher Fauletb61caf42020-04-21 10:57:42 +02006383 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006384 if (var == NULL) {
6385 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6386 goto error;
6387 }
6388 var->data.type = SMP_T_STR;
6389 var->data.u.str.area = hdr;
6390 var->data.u.str.data = 4;
6391 LIST_INIT(&var->list);
6392 LIST_ADDQ(&rules->preset_vars, &var->list);
6393 hdr = NULL;
6394 var = NULL;
6395
Christopher Fauletb61caf42020-04-21 10:57:42 +02006396 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006397 if (var == NULL) {
6398 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6399 goto error;
6400 }
6401 var->data.type = SMP_T_STR;
6402 var->data.u.str.area = user;
6403 var->data.u.str.data = strlen(user);
6404 LIST_INIT(&var->list);
6405 LIST_ADDQ(&rules->preset_vars, &var->list);
6406 user = NULL;
6407 var = NULL;
6408 }
6409
Christopher Faulet61cc8522020-04-20 14:54:42 +02006410 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006411 if (rs)
6412 goto ruleset_found;
6413
Christopher Faulet61cc8522020-04-20 14:54:42 +02006414 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006415 if (rs == NULL) {
6416 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6417 goto error;
6418 }
6419
6420 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6421 1, curpx, &rs->rules, file, line, &errmsg);
6422 if (!chk) {
6423 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6424 goto error;
6425 }
6426 chk->index = index++;
6427 LIST_ADDQ(&rs->rules, &chk->list);
6428
6429 if (mysql_req) {
6430 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6431 1, curpx, &rs->rules, file, line, &errmsg);
6432 if (!chk) {
6433 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6434 goto error;
6435 }
6436 chk->index = index++;
6437 LIST_ADDQ(&rs->rules, &chk->list);
6438 }
6439
6440 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006441 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006442 if (!chk) {
6443 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6444 goto error;
6445 }
6446 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6447 chk->index = index++;
6448 LIST_ADDQ(&rs->rules, &chk->list);
6449
6450 if (mysql_req) {
6451 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006452 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006453 if (!chk) {
6454 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6455 goto error;
6456 }
6457 chk->expect.custom = tcpcheck_mysql_expect_ok;
6458 chk->index = index++;
6459 LIST_ADDQ(&rs->rules, &chk->list);
6460 }
6461
Christopher Fauletd7cee712020-04-21 13:45:00 +02006462 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006463
6464 ruleset_found:
6465 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006466 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006467
6468 out:
6469 free(errmsg);
6470 return err_code;
6471
6472 error:
6473 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006474 free(user);
6475 free(var);
6476 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006477 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006478 err_code |= ERR_ALERT | ERR_FATAL;
6479 goto out;
6480}
6481
Christopher Faulet1997eca2020-04-03 23:13:50 +02006482int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6483 const char *file, int line)
6484{
6485 static char *ldap_req = "300C020101600702010304008000";
6486
6487 struct tcpcheck_ruleset *rs = NULL;
6488 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6489 struct tcpcheck_rule *chk;
6490 char *errmsg = NULL;
6491 int err_code = 0;
6492
6493 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6494 err_code |= ERR_WARN;
6495
6496 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6497 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006498
6499 curpx->options2 &= ~PR_O2_CHK_ANY;
6500 curpx->options2 |= PR_O2_TCPCHK_CHK;
6501
6502 free_tcpcheck_vars(&rules->preset_vars);
6503 rules->list = NULL;
6504 rules->flags = 0;
6505
Christopher Faulet61cc8522020-04-20 14:54:42 +02006506 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006507 if (rs)
6508 goto ruleset_found;
6509
Christopher Faulet61cc8522020-04-20 14:54:42 +02006510 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006511 if (rs == NULL) {
6512 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6513 goto error;
6514 }
6515
6516 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6517 1, curpx, &rs->rules, file, line, &errmsg);
6518 if (!chk) {
6519 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6520 goto error;
6521 }
6522 chk->index = 0;
6523 LIST_ADDQ(&rs->rules, &chk->list);
6524
6525 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6526 "min-recv", "14",
6527 "on-error", "Not LDAPv3 protocol",
6528 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006529 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006530 if (!chk) {
6531 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6532 goto error;
6533 }
6534 chk->index = 1;
6535 LIST_ADDQ(&rs->rules, &chk->list);
6536
6537 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006538 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006539 if (!chk) {
6540 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6541 goto error;
6542 }
6543 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6544 chk->index = 2;
6545 LIST_ADDQ(&rs->rules, &chk->list);
6546
Christopher Fauletd7cee712020-04-21 13:45:00 +02006547 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006548
6549 ruleset_found:
6550 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006551 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006552
6553 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006554 free(errmsg);
6555 return err_code;
6556
6557 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006558 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006559 err_code |= ERR_ALERT | ERR_FATAL;
6560 goto out;
6561}
6562
6563int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6564 const char *file, int line)
6565{
6566 struct tcpcheck_ruleset *rs = NULL;
6567 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6568 struct tcpcheck_rule *chk;
6569 char *spop_req = NULL;
6570 char *errmsg = NULL;
6571 int spop_len = 0, err_code = 0;
6572
6573 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6574 err_code |= ERR_WARN;
6575
6576 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6577 goto out;
6578
Christopher Faulet267b01b2020-04-04 10:27:09 +02006579 curpx->options2 &= ~PR_O2_CHK_ANY;
6580 curpx->options2 |= PR_O2_TCPCHK_CHK;
6581
6582 free_tcpcheck_vars(&rules->preset_vars);
6583 rules->list = NULL;
6584 rules->flags = 0;
6585
6586
Christopher Faulet61cc8522020-04-20 14:54:42 +02006587 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006588 if (rs)
6589 goto ruleset_found;
6590
Christopher Faulet61cc8522020-04-20 14:54:42 +02006591 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006592 if (rs == NULL) {
6593 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6594 goto error;
6595 }
6596
6597 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6598 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6599 goto error;
6600 }
6601 chunk_reset(&trash);
6602 dump_binary(&trash, spop_req, spop_len);
6603 trash.area[trash.data] = '\0';
6604
6605 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6606 1, curpx, &rs->rules, file, line, &errmsg);
6607 if (!chk) {
6608 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6609 goto error;
6610 }
6611 chk->index = 0;
6612 LIST_ADDQ(&rs->rules, &chk->list);
6613
6614 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006615 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006616 if (!chk) {
6617 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6618 goto error;
6619 }
6620 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6621 chk->index = 1;
6622 LIST_ADDQ(&rs->rules, &chk->list);
6623
Christopher Fauletd7cee712020-04-21 13:45:00 +02006624 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006625
6626 ruleset_found:
6627 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006628 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006629
6630 out:
6631 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006632 free(errmsg);
6633 return err_code;
6634
6635 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006636 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006637 err_code |= ERR_ALERT | ERR_FATAL;
6638 goto out;
6639}
Christopher Fauletce355072020-04-02 11:44:39 +02006640
Christopher Faulete5870d82020-04-15 11:32:03 +02006641
6642struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6643{
6644 struct tcpcheck_rule *chk = NULL;
6645 struct tcpcheck_http_hdr *hdr = NULL;
6646 char *meth = NULL, *uri = NULL, *vsn = NULL;
6647 char *hdrs, *body;
6648
6649 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6650 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6651 if (hdrs == body)
6652 hdrs = NULL;
6653 if (hdrs) {
6654 *hdrs = '\0';
6655 hdrs +=2;
6656 }
6657 if (body) {
6658 *body = '\0';
6659 body += 4;
6660 }
6661 if (hdrs || body) {
6662 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6663 " Please, consider to use 'http-check send' directive instead.");
6664 }
6665
6666 chk = calloc(1, sizeof(*chk));
6667 if (!chk) {
6668 memprintf(errmsg, "out of memory");
6669 goto error;
6670 }
6671 chk->action = TCPCHK_ACT_SEND;
6672 chk->send.type = TCPCHK_SEND_HTTP;
6673 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6674 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6675 LIST_INIT(&chk->send.http.hdrs);
6676
6677 /* Copy the method, uri and version */
6678 if (*args[cur_arg]) {
6679 if (!*args[cur_arg+1])
6680 uri = args[cur_arg];
6681 else
6682 meth = args[cur_arg];
6683 }
6684 if (*args[cur_arg+1])
6685 uri = args[cur_arg+1];
6686 if (*args[cur_arg+2])
6687 vsn = args[cur_arg+2];
6688
6689 if (meth) {
6690 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6691 chk->send.http.meth.str.area = strdup(meth);
6692 chk->send.http.meth.str.data = strlen(meth);
6693 if (!chk->send.http.meth.str.area) {
6694 memprintf(errmsg, "out of memory");
6695 goto error;
6696 }
6697 }
6698 if (uri) {
6699 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006700 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006701 memprintf(errmsg, "out of memory");
6702 goto error;
6703 }
6704 }
6705 if (vsn) {
6706 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006707 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006708 memprintf(errmsg, "out of memory");
6709 goto error;
6710 }
6711 }
6712
6713 /* Copy the header */
6714 if (hdrs) {
6715 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6716 struct h1m h1m;
6717 int i, ret;
6718
6719 /* Build and parse the request */
6720 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6721
6722 h1m.flags = H1_MF_HDRS_ONLY;
6723 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6724 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6725 &h1m, NULL);
6726 if (ret <= 0) {
6727 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6728 goto error;
6729 }
6730
Christopher Fauletb61caf42020-04-21 10:57:42 +02006731 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006732 hdr = calloc(1, sizeof(*hdr));
6733 if (!hdr) {
6734 memprintf(errmsg, "out of memory");
6735 goto error;
6736 }
6737 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006738 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006739 if (!hdr->name.ptr) {
6740 memprintf(errmsg, "out of memory");
6741 goto error;
6742 }
6743
Christopher Fauletb61caf42020-04-21 10:57:42 +02006744 ist0(tmp_hdrs[i].v);
6745 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 +02006746 goto error;
6747 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6748 }
6749 }
6750
6751 /* Copy the body */
6752 if (body) {
6753 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006754 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006755 memprintf(errmsg, "out of memory");
6756 goto error;
6757 }
6758 }
6759
6760 return chk;
6761
6762 error:
6763 free_tcpcheck_http_hdr(hdr);
6764 free_tcpcheck(chk, 0);
6765 return NULL;
6766}
6767
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006768int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6769 const char *file, int line)
6770{
Christopher Faulete5870d82020-04-15 11:32:03 +02006771 struct tcpcheck_ruleset *rs = NULL;
6772 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6773 struct tcpcheck_rule *chk;
6774 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006775 int err_code = 0;
6776
6777 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6778 err_code |= ERR_WARN;
6779
6780 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6781 goto out;
6782
Christopher Faulete5870d82020-04-15 11:32:03 +02006783 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6784 if (!chk) {
6785 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6786 goto error;
6787 }
6788 if (errmsg) {
6789 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6790 err_code |= ERR_WARN;
6791 free(errmsg);
6792 errmsg = NULL;
6793 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006794
Christopher Faulete5870d82020-04-15 11:32:03 +02006795 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006796 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006797 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006798
Christopher Faulete5870d82020-04-15 11:32:03 +02006799 free_tcpcheck_vars(&rules->preset_vars);
6800 rules->list = NULL;
6801 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006802
Christopher Faulete5870d82020-04-15 11:32:03 +02006803 /* Deduce the ruleset name from the proxy info */
6804 chunk_printf(&trash, "*http-check-%s_%s-%d",
6805 ((curpx == defpx) ? "defaults" : curpx->id),
6806 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006807
Christopher Faulet61cc8522020-04-20 14:54:42 +02006808 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006809 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006810 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006811 if (rs == NULL) {
6812 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6813 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006814 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006815 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006816
Christopher Faulete5870d82020-04-15 11:32:03 +02006817 rules->list = &rs->rules;
6818 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6819 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6820 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6821 rules->list = NULL;
6822 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006823 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006824
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006825 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006826 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006827 return err_code;
6828
6829 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006830 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006831 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006832 err_code |= ERR_ALERT | ERR_FATAL;
6833 goto out;
6834}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006835
Christopher Faulet6f557912020-04-09 15:58:50 +02006836int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6837 const char *file, int line)
6838{
6839 int err_code = 0;
6840
Christopher Faulet6f557912020-04-09 15:58:50 +02006841 curpx->options2 &= ~PR_O2_CHK_ANY;
6842 curpx->options2 |= PR_O2_EXT_CHK;
6843 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6844 goto out;
6845
6846 out:
6847 return err_code;
6848}
6849
Christopher Fauletce8111e2020-04-06 15:04:11 +02006850/* Parse the "addr" server keyword */
6851static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6852 char **errmsg)
6853{
6854 struct sockaddr_storage *sk;
6855 struct protocol *proto;
6856 int port1, port2, err_code = 0;
6857
6858
6859 if (!*args[*cur_arg+1]) {
6860 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6861 goto error;
6862 }
6863
6864 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6865 if (!sk) {
6866 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6867 goto error;
6868 }
6869
6870 proto = protocol_by_family(sk->ss_family);
6871 if (!proto || !proto->connect) {
6872 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6873 args[*cur_arg], args[*cur_arg+1]);
6874 goto error;
6875 }
6876
6877 if (port1 != port2) {
6878 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6879 args[*cur_arg], args[*cur_arg+1]);
6880 goto error;
6881 }
6882
6883 srv->check.addr = srv->agent.addr = *sk;
6884 srv->flags |= SRV_F_CHECKADDR;
6885 srv->flags |= SRV_F_AGENTADDR;
6886
6887 out:
6888 return err_code;
6889
6890 error:
6891 err_code |= ERR_ALERT | ERR_FATAL;
6892 goto out;
6893}
6894
6895
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006896/* Parse the "agent-addr" server keyword */
6897static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6898 char **errmsg)
6899{
6900 int err_code = 0;
6901
6902 if (!*(args[*cur_arg+1])) {
6903 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6904 goto error;
6905 }
6906 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6907 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6908 goto error;
6909 }
6910
6911 out:
6912 return err_code;
6913
6914 error:
6915 err_code |= ERR_ALERT | ERR_FATAL;
6916 goto out;
6917}
6918
6919/* Parse the "agent-check" server keyword */
6920static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6921 char **errmsg)
6922{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006923 struct tcpcheck_ruleset *rs = NULL;
6924 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6925 struct tcpcheck_rule *chk;
6926 int err_code = 0;
6927
6928 if (srv->do_agent)
6929 goto out;
6930
6931 if (!rules) {
6932 rules = calloc(1, sizeof(*rules));
6933 if (!rules) {
6934 memprintf(errmsg, "out of memory.");
6935 goto error;
6936 }
6937 LIST_INIT(&rules->preset_vars);
6938 srv->agent.tcpcheck_rules = rules;
6939 }
6940 rules->list = NULL;
6941 rules->flags = 0;
6942
Christopher Faulet61cc8522020-04-20 14:54:42 +02006943 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006944 if (rs)
6945 goto ruleset_found;
6946
Christopher Faulet61cc8522020-04-20 14:54:42 +02006947 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006948 if (rs == NULL) {
6949 memprintf(errmsg, "out of memory.");
6950 goto error;
6951 }
6952
6953 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6954 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6955 if (!chk) {
6956 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6957 goto error;
6958 }
6959 chk->index = 0;
6960 LIST_ADDQ(&rs->rules, &chk->list);
6961
6962 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006963 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6964 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006965 if (!chk) {
6966 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6967 goto error;
6968 }
6969 chk->expect.custom = tcpcheck_agent_expect_reply;
6970 chk->index = 1;
6971 LIST_ADDQ(&rs->rules, &chk->list);
6972
Christopher Fauletd7cee712020-04-21 13:45:00 +02006973 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006974
6975 ruleset_found:
6976 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006977 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006978 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006979
6980 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006981 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006982
6983 error:
6984 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006985 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006986 err_code |= ERR_ALERT | ERR_FATAL;
6987 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006988}
6989
6990/* Parse the "agent-inter" server keyword */
6991static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6992 char **errmsg)
6993{
6994 const char *err = NULL;
6995 unsigned int delay;
6996 int err_code = 0;
6997
6998 if (!*(args[*cur_arg+1])) {
6999 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7000 goto error;
7001 }
7002
7003 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7004 if (err == PARSE_TIME_OVER) {
7005 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7006 args[*cur_arg+1], args[*cur_arg], srv->id);
7007 goto error;
7008 }
7009 else if (err == PARSE_TIME_UNDER) {
7010 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7011 args[*cur_arg+1], args[*cur_arg], srv->id);
7012 goto error;
7013 }
7014 else if (err) {
7015 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7016 *err, srv->id);
7017 goto error;
7018 }
7019 if (delay <= 0) {
7020 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7021 delay, args[*cur_arg], srv->id);
7022 goto error;
7023 }
7024 srv->agent.inter = delay;
7025
7026 out:
7027 return err_code;
7028
7029 error:
7030 err_code |= ERR_ALERT | ERR_FATAL;
7031 goto out;
7032}
7033
7034/* Parse the "agent-port" server keyword */
7035static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7036 char **errmsg)
7037{
7038 int err_code = 0;
7039
7040 if (!*(args[*cur_arg+1])) {
7041 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7042 goto error;
7043 }
7044
7045 global.maxsock++;
7046 srv->agent.port = atol(args[*cur_arg+1]);
7047
7048 out:
7049 return err_code;
7050
7051 error:
7052 err_code |= ERR_ALERT | ERR_FATAL;
7053 goto out;
7054}
7055
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007056int set_srv_agent_send(struct server *srv, const char *send)
7057{
7058 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7059 struct tcpcheck_var *var = NULL;
7060 char *str;
7061
7062 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007063 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007064 if (str == NULL || var == NULL)
7065 goto error;
7066
7067 free_tcpcheck_vars(&rules->preset_vars);
7068
7069 var->data.type = SMP_T_STR;
7070 var->data.u.str.area = str;
7071 var->data.u.str.data = strlen(str);
7072 LIST_INIT(&var->list);
7073 LIST_ADDQ(&rules->preset_vars, &var->list);
7074
7075 return 1;
7076
7077 error:
7078 free(str);
7079 free(var);
7080 return 0;
7081}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007082
7083/* Parse the "agent-send" server keyword */
7084static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7085 char **errmsg)
7086{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007087 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007088 int err_code = 0;
7089
7090 if (!*(args[*cur_arg+1])) {
7091 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7092 goto error;
7093 }
7094
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007095 if (!rules) {
7096 rules = calloc(1, sizeof(*rules));
7097 if (!rules) {
7098 memprintf(errmsg, "out of memory.");
7099 goto error;
7100 }
7101 LIST_INIT(&rules->preset_vars);
7102 srv->agent.tcpcheck_rules = rules;
7103 }
7104
7105 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007106 memprintf(errmsg, "out of memory.");
7107 goto error;
7108 }
7109
7110 out:
7111 return err_code;
7112
7113 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007114 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007115 err_code |= ERR_ALERT | ERR_FATAL;
7116 goto out;
7117}
7118
7119/* Parse the "no-agent-send" server keyword */
7120static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7121 char **errmsg)
7122{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007123 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007124 return 0;
7125}
7126
Christopher Fauletce8111e2020-04-06 15:04:11 +02007127/* Parse the "check" server keyword */
7128static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7129 char **errmsg)
7130{
7131 srv->do_check = 1;
7132 return 0;
7133}
7134
7135/* Parse the "check-send-proxy" server keyword */
7136static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7137 char **errmsg)
7138{
7139 srv->check.send_proxy = 1;
7140 return 0;
7141}
7142
7143/* Parse the "check-via-socks4" server keyword */
7144static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7145 char **errmsg)
7146{
7147 srv->check.via_socks4 = 1;
7148 return 0;
7149}
7150
7151/* Parse the "no-check" server keyword */
7152static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7153 char **errmsg)
7154{
7155 deinit_srv_check(srv);
7156 return 0;
7157}
7158
7159/* Parse the "no-check-send-proxy" server keyword */
7160static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7161 char **errmsg)
7162{
7163 srv->check.send_proxy = 0;
7164 return 0;
7165}
7166
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007167/* parse the "check-proto" server keyword */
7168static int srv_parse_check_proto(char **args, int *cur_arg,
7169 struct proxy *px, struct server *newsrv, char **err)
7170{
7171 int err_code = 0;
7172
7173 if (!*args[*cur_arg + 1]) {
7174 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7175 goto error;
7176 }
7177 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7178 if (!newsrv->check.mux_proto) {
7179 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7180 goto error;
7181 }
7182
7183 out:
7184 return err_code;
7185
7186 error:
7187 err_code |= ERR_ALERT | ERR_FATAL;
7188 goto out;
7189}
7190
7191
Christopher Fauletce8111e2020-04-06 15:04:11 +02007192/* Parse the "rise" server keyword */
7193static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7194 char **errmsg)
7195{
7196 int err_code = 0;
7197
7198 if (!*args[*cur_arg + 1]) {
7199 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7200 goto error;
7201 }
7202
7203 srv->check.rise = atol(args[*cur_arg+1]);
7204 if (srv->check.rise <= 0) {
7205 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7206 goto error;
7207 }
7208
7209 if (srv->check.health)
7210 srv->check.health = srv->check.rise;
7211
7212 out:
7213 return err_code;
7214
7215 error:
7216 deinit_srv_agent_check(srv);
7217 err_code |= ERR_ALERT | ERR_FATAL;
7218 goto out;
7219 return 0;
7220}
7221
7222/* Parse the "fall" server keyword */
7223static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7224 char **errmsg)
7225{
7226 int err_code = 0;
7227
7228 if (!*args[*cur_arg + 1]) {
7229 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7230 goto error;
7231 }
7232
7233 srv->check.fall = atol(args[*cur_arg+1]);
7234 if (srv->check.fall <= 0) {
7235 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7236 goto error;
7237 }
7238
7239 out:
7240 return err_code;
7241
7242 error:
7243 deinit_srv_agent_check(srv);
7244 err_code |= ERR_ALERT | ERR_FATAL;
7245 goto out;
7246 return 0;
7247}
7248
7249/* Parse the "inter" server keyword */
7250static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7251 char **errmsg)
7252{
7253 const char *err = NULL;
7254 unsigned int delay;
7255 int err_code = 0;
7256
7257 if (!*(args[*cur_arg+1])) {
7258 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7259 goto error;
7260 }
7261
7262 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7263 if (err == PARSE_TIME_OVER) {
7264 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7265 args[*cur_arg+1], args[*cur_arg], srv->id);
7266 goto error;
7267 }
7268 else if (err == PARSE_TIME_UNDER) {
7269 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7270 args[*cur_arg+1], args[*cur_arg], srv->id);
7271 goto error;
7272 }
7273 else if (err) {
7274 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7275 *err, srv->id);
7276 goto error;
7277 }
7278 if (delay <= 0) {
7279 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7280 delay, args[*cur_arg], srv->id);
7281 goto error;
7282 }
7283 srv->check.inter = delay;
7284
7285 out:
7286 return err_code;
7287
7288 error:
7289 err_code |= ERR_ALERT | ERR_FATAL;
7290 goto out;
7291}
7292
7293
7294/* Parse the "fastinter" server keyword */
7295static int srv_parse_check_fastinter(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->check.fastinter = delay;
7329
7330 out:
7331 return err_code;
7332
7333 error:
7334 err_code |= ERR_ALERT | ERR_FATAL;
7335 goto out;
7336}
7337
7338
7339/* Parse the "downinter" server keyword */
7340static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7341 char **errmsg)
7342{
7343 const char *err = NULL;
7344 unsigned int delay;
7345 int err_code = 0;
7346
7347 if (!*(args[*cur_arg+1])) {
7348 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7349 goto error;
7350 }
7351
7352 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7353 if (err == PARSE_TIME_OVER) {
7354 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7355 args[*cur_arg+1], args[*cur_arg], srv->id);
7356 goto error;
7357 }
7358 else if (err == PARSE_TIME_UNDER) {
7359 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7360 args[*cur_arg+1], args[*cur_arg], srv->id);
7361 goto error;
7362 }
7363 else if (err) {
7364 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7365 *err, srv->id);
7366 goto error;
7367 }
7368 if (delay <= 0) {
7369 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7370 delay, args[*cur_arg], srv->id);
7371 goto error;
7372 }
7373 srv->check.downinter = delay;
7374
7375 out:
7376 return err_code;
7377
7378 error:
7379 err_code |= ERR_ALERT | ERR_FATAL;
7380 goto out;
7381}
7382
7383/* Parse the "port" server keyword */
7384static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7385 char **errmsg)
7386{
7387 int err_code = 0;
7388
7389 if (!*(args[*cur_arg+1])) {
7390 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7391 goto error;
7392 }
7393
7394 global.maxsock++;
7395 srv->check.port = atol(args[*cur_arg+1]);
7396 srv->flags |= SRV_F_CHECKPORT;
7397
7398 out:
7399 return err_code;
7400
7401 error:
7402 err_code |= ERR_ALERT | ERR_FATAL;
7403 goto out;
7404}
7405
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007406static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007407 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7408 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7409 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007410 { 0, NULL, NULL },
7411}};
7412
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007413static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007414 { "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 +02007415 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7416 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7417 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7418 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7419 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007420 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007421 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007422 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7423 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007424 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007425 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7426 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7427 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7428 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7429 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7430 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7431 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7432 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007433 { NULL, NULL, 0 },
7434}};
7435
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007436INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007437INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007438
Willy Tarreaubd741542010-03-16 18:46:54 +01007439/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007440 * Local variables:
7441 * c-indent-level: 8
7442 * c-basic-offset: 8
7443 * End:
7444 */