blob: c805c2bab32d869e3aad347d1b5ad5ed057d3e7d [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 Fauletb61caf42020-04-21 10:57:42 +0200604 chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
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) {
784 case TCPCHK_EXPECT_STRING:
785 case TCPCHK_EXPECT_BINARY:
786 case TCPCHK_EXPECT_HTTP_STATUS:
787 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200788 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 break;
790 case TCPCHK_EXPECT_REGEX:
791 case TCPCHK_EXPECT_REGEX_BINARY:
792 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
793 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
794 regex_free(rule->expect.regex);
795 break;
796 case TCPCHK_EXPECT_CUSTOM:
797 case TCPCHK_EXPECT_UNDEF:
798 break;
799 }
800 break;
801 case TCPCHK_ACT_CONNECT:
802 free(rule->connect.sni);
803 free(rule->connect.alpn);
804 release_sample_expr(rule->connect.port_expr);
805 break;
806 case TCPCHK_ACT_COMMENT:
807 break;
808 case TCPCHK_ACT_ACTION_KW:
809 free(rule->action_kw.rule);
810 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200811 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200812
813 if (in_pool)
814 pool_free(pool_head_tcpcheck_rule, rule);
815 else
816 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200817}
818
Christopher Faulet61cc8522020-04-20 14:54:42 +0200819/* Creates a tcp-check variable used in preset variables before executing a
820 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100821 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200822static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200824 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 var = calloc(1, sizeof(*var));
827 if (var == NULL)
828 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100829
Christopher Fauletb61caf42020-04-21 10:57:42 +0200830 var->name = istdup(name);
831 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200832 free(var);
833 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100834 }
Simon Horman98637e52014-06-20 12:30:16 +0900835
Christopher Faulet61cc8522020-04-20 14:54:42 +0200836 LIST_INIT(&var->list);
837 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900838}
839
Christopher Faulet61cc8522020-04-20 14:54:42 +0200840/* Releases memory allocated for a preset tcp-check variable */
841static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900842{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200843 if (!var)
844 return;
845
Christopher Fauletb61caf42020-04-21 10:57:42 +0200846 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200847 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
848 free(var->data.u.str.area);
849 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
850 free(var->data.u.meth.str.area);
851 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900852}
853
Christopher Faulet61cc8522020-04-20 14:54:42 +0200854/* Releases a list of preset tcp-check variables */
855static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900856{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200857 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200858
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 list_for_each_entry_safe(var, back, vars, list) {
860 LIST_DEL(&var->list);
861 free_tcpcheck_var(var);
862 }
Simon Horman98637e52014-06-20 12:30:16 +0900863}
864
Christopher Faulet61cc8522020-04-20 14:54:42 +0200865/* Duplicate a list of preset tcp-check variables */
866int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900867{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200868 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900869
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200871 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 if (!new)
873 goto error;
874 new->data.type = var->data.type;
875 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
876 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
877 goto error;
878 if (var->data.type == SMP_T_STR)
879 new->data.u.str.area[new->data.u.str.data] = 0;
880 }
881 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
882 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
883 goto error;
884 new->data.u.str.area[new->data.u.str.data] = 0;
885 new->data.u.meth.meth = var->data.u.meth.meth;
886 }
887 else
888 new->data.u = var->data.u;
889 LIST_ADDQ(dst, &new->list);
890 }
891 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900892
Christopher Faulet61cc8522020-04-20 14:54:42 +0200893 error:
894 free(new);
895 return 0;
896}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200897
Christopher Faulet61cc8522020-04-20 14:54:42 +0200898/* Looks for a shared tcp-check ruleset given its name. */
899static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
900{
901 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200902 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900903
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
905 if (node) {
906 rs = container_of(node, typeof(*rs), node);
907 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200908 }
909 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900910}
911
Christopher Faulet61cc8522020-04-20 14:54:42 +0200912/* Creates a new shared tcp-check ruleset */
913static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900914{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200915 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900916
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 rs = calloc(1, sizeof(*rs));
918 if (rs == NULL)
919 return NULL;
920
Christopher Fauletd7cee712020-04-21 13:45:00 +0200921 rs->node.key = strdup(name);
922 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200923 free(rs);
924 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900925 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200926
Christopher Faulet61cc8522020-04-20 14:54:42 +0200927 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200928 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900930}
931
Christopher Faulet61cc8522020-04-20 14:54:42 +0200932/* Releases memory allocated by a tcp-check ruleset. */
933static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900934{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200935 struct tcpcheck_rule *r, *rb;
936 if (!rs)
937 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200938
Christopher Fauletd7cee712020-04-21 13:45:00 +0200939 ebpt_delete(&rs->node);
940 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200941 list_for_each_entry_safe(r, rb, &rs->rules, list) {
942 LIST_DEL(&r->list);
943 free_tcpcheck(r, 0);
944 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200945 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900946}
947
Christopher Faulet61cc8522020-04-20 14:54:42 +0200948
949/**************************************************************************/
950/**************** Everything about tcp-checks execution *******************/
951/**************************************************************************/
952/* Returns the id of a step in a tcp-check ruleset */
953static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200954{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200955 if (!rule)
956 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900957
Christopher Faulet61cc8522020-04-20 14:54:42 +0200958 /* no last started step => first step */
959 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900960 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900961
Christopher Faulet61cc8522020-04-20 14:54:42 +0200962 /* last step is the first implicit connect */
963 if (rule->index == 0 &&
964 rule->action == TCPCHK_ACT_CONNECT &&
965 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
966 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900967
Christopher Faulet61cc8522020-04-20 14:54:42 +0200968 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900969}
970
Christopher Faulet61cc8522020-04-20 14:54:42 +0200971/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
972 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100973 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200974static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 list_for_each_entry(r, rules->list, list) {
979 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
980 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100981 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200982 return NULL;
983}
Cyril Bontéac92a062014-12-27 22:28:38 +0100984
Christopher Faulet61cc8522020-04-20 14:54:42 +0200985/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
986 * NULL if none was found.
987 */
988static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
989{
990 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100991
Christopher Faulet61cc8522020-04-20 14:54:42 +0200992 list_for_each_entry_rev(r, rules->list, list) {
993 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
994 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100995 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200996 return NULL;
997}
Cyril Bontéac92a062014-12-27 22:28:38 +0100998
Christopher Faulet61cc8522020-04-20 14:54:42 +0200999/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1000 * <start> or NULL if non was found. If <start> is NULL, it relies on
1001 * get_first_tcpcheck_rule().
1002 */
1003static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1004{
1005 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001006
Christopher Faulet61cc8522020-04-20 14:54:42 +02001007 if (!start)
1008 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001009
Christopher Faulet61cc8522020-04-20 14:54:42 +02001010 r = LIST_NEXT(&start->list, typeof(r), list);
1011 list_for_each_entry_from(r, rules->list, list) {
1012 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1013 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001014 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001015 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016}
Simon Horman98637e52014-06-20 12:30:16 +09001017
Simon Horman98637e52014-06-20 12:30:16 +09001018
Christopher Faulet61cc8522020-04-20 14:54:42 +02001019/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1020static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1021 int match, struct ist info)
1022{
1023 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001024
Christopher Faulet61cc8522020-04-20 14:54:42 +02001025 /* Follows these step to produce the info message:
1026 * 1. if info field is already provided, copy it
1027 * 2. if the expect rule provides an onerror log-format string,
1028 * use it to produce the message
1029 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1030 * 4. Otherwise produce the generic tcp-check info message
1031 */
1032 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001033 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001034 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001035 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1037 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1038 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001039 }
Simon Horman98637e52014-06-20 12:30:16 +09001040
Christopher Faulet61cc8522020-04-20 14:54:42 +02001041 if (check->type == PR_O2_TCPCHK_CHK &&
1042 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1043 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001044
Christopher Faulet61cc8522020-04-20 14:54:42 +02001045 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1046 switch (rule->expect.type) {
1047 case TCPCHK_EXPECT_STRING:
1048 case TCPCHK_EXPECT_HTTP_STATUS:
1049 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001050 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001051 tcpcheck_get_step_id(check, rule));
1052 break;
1053 case TCPCHK_EXPECT_BINARY:
1054 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1055 break;
1056 case TCPCHK_EXPECT_REGEX:
1057 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1058 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1059 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1060 break;
1061 case TCPCHK_EXPECT_REGEX_BINARY:
1062 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Simon Horman98637e52014-06-20 12:30:16 +09001063
Christopher Faulet61cc8522020-04-20 14:54:42 +02001064 /* If references to the matched text were made, divide the
1065 * offsets by 2 to match offset of the original response buffer.
1066 */
1067 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
1068 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001069
Christopher Faulet61cc8522020-04-20 14:54:42 +02001070 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
1071 pmatch[i].rm_so /= 2; /* at first matched char. */
1072 pmatch[i].rm_eo /= 2; /* at last matched char. */
1073 }
1074 }
1075 break;
1076 case TCPCHK_EXPECT_CUSTOM:
1077 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1078 break;
1079 case TCPCHK_EXPECT_UNDEF:
1080 /* Should never happen. */
1081 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001082 }
1083
Christopher Faulet61cc8522020-04-20 14:54:42 +02001084 comment:
1085 /* If the failing expect rule provides a comment, it is concatenated to
1086 * the info message.
1087 */
1088 if (rule->comment) {
1089 chunk_strcat(msg, " comment: ");
1090 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
1091 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
1092 if (ret != -1) /* ignore comment if too large */
1093 msg->data += ret;
1094 }
1095 else
1096 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001097 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001098
Christopher Faulet61cc8522020-04-20 14:54:42 +02001099 /* Finally, the check status code is set if the failing expect rule
1100 * defines a status expression.
1101 */
1102 if (rule->expect.status_expr) {
1103 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1104 rule->expect.status_expr, SMP_T_SINT);
1105 if (smp)
1106 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001107 }
Simon Horman98637e52014-06-20 12:30:16 +09001108
Christopher Faulet61cc8522020-04-20 14:54:42 +02001109 *(b_tail(msg)) = '\0';
1110}
Cyril Bontéac92a062014-12-27 22:28:38 +01001111
Christopher Faulet61cc8522020-04-20 14:54:42 +02001112/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1113static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1114 struct ist info)
1115{
1116 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001117
Christopher Faulet61cc8522020-04-20 14:54:42 +02001118 /* Follows these step to produce the info message:
1119 * 1. if info field is already provided, copy it
1120 * 2. if the expect rule provides an onsucces log-format string,
1121 * use it to produce the message
1122 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1123 * 4. Otherwise produce the generic tcp-check info message
1124 */
1125 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001126 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001127 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1128 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1129 &rule->expect.onsuccess_fmt);
1130 else if (check->type == PR_O2_TCPCHK_CHK &&
1131 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1132 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001133
Christopher Faulet61cc8522020-04-20 14:54:42 +02001134 /* Finally, the check status code is set if the expect rule defines a
1135 * status expression.
1136 */
1137 if (rule->expect.status_expr) {
1138 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1139 rule->expect.status_expr, SMP_T_SINT);
1140 if (smp)
1141 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001142 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001143
1144 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001145}
1146
Christopher Faulet61cc8522020-04-20 14:54:42 +02001147/* Builds the server state header used by HTTP health-checks */
1148static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001149{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001150 int sv_state;
1151 int ratio;
1152 char addr[46];
1153 char port[6];
1154 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1155 "UP %d/%d", "UP",
1156 "NOLB %d/%d", "NOLB",
1157 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001158
Christopher Faulet61cc8522020-04-20 14:54:42 +02001159 if (!(s->check.state & CHK_ST_ENABLED))
1160 sv_state = 6;
1161 else if (s->cur_state != SRV_ST_STOPPED) {
1162 if (s->check.health == s->check.rise + s->check.fall - 1)
1163 sv_state = 3; /* UP */
1164 else
1165 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001166
Christopher Faulet61cc8522020-04-20 14:54:42 +02001167 if (s->cur_state == SRV_ST_STOPPING)
1168 sv_state += 2;
1169 } else {
1170 if (s->check.health)
1171 sv_state = 1; /* going up */
1172 else
1173 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001174 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001175
Christopher Faulet61cc8522020-04-20 14:54:42 +02001176 chunk_appendf(buf, srv_hlt_st[sv_state],
1177 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1178 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 addr_to_str(&s->addr, addr, sizeof(addr));
1181 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1182 snprintf(port, sizeof(port), "%u", s->svc_port);
1183 else
1184 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001185
Christopher Faulet61cc8522020-04-20 14:54:42 +02001186 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1187 addr, port, s->proxy->id, s->id,
1188 global.node,
1189 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1190 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1191 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1192 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001193
Christopher Faulet61cc8522020-04-20 14:54:42 +02001194 if ((s->cur_state == SRV_ST_STARTING) &&
1195 now.tv_sec < s->last_change + s->slowstart &&
1196 now.tv_sec >= s->last_change) {
1197 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1198 chunk_appendf(buf, "; throttle=%d%%", ratio);
1199 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001200
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 return b_data(buf);
1202}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204/* Internal functions to parse and validate a MySQL packet in the context of an
1205 * expect rule. It start to parse the input buffer at the offset <offset>. If
1206 * <last_read> is set, no more data are expected.
1207 */
1208static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1209 unsigned int offset, int last_read)
1210{
1211 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1212 enum healthcheck_status status;
1213 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001214 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001215 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001216
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 /* 3 Bytes for the packet length and 1 byte for the sequence id */
1219 if (!last_read && b_data(&check->bi) < offset+4) {
1220 if (!last_read)
1221 goto wait_more_data;
1222
1223 /* invalid length or truncated response */
1224 status = HCHK_STATUS_L7RSP;
1225 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001226 }
1227
Christopher Faulet61cc8522020-04-20 14:54:42 +02001228 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1229 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1230 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001231
Christopher Faulet61cc8522020-04-20 14:54:42 +02001232 if (b_data(&check->bi) < offset+plen+4) {
1233 if (!last_read)
1234 goto wait_more_data;
1235
1236 /* invalid length or truncated response */
1237 status = HCHK_STATUS_L7RSP;
1238 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 if (*b_peek(&check->bi, offset+4) == '\xff') {
1242 /* MySQL Error packet always begin with field_count = 0xff */
1243 status = HCHK_STATUS_L7STS;
1244 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1245 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1246 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1247 goto error;
1248 }
Simon Horman98637e52014-06-20 12:30:16 +09001249
Christopher Faulet61cc8522020-04-20 14:54:42 +02001250 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1251 /* Not the last rule, continue */
1252 goto out;
1253 }
Simon Horman98637e52014-06-20 12:30:16 +09001254
Christopher Faulet61cc8522020-04-20 14:54:42 +02001255 /* We set the MySQL Version in description for information purpose
1256 * FIXME : it can be cool to use MySQL Version for other purpose,
1257 * like mark as down old MySQL server.
1258 */
1259 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001260
Christopher Faulet61cc8522020-04-20 14:54:42 +02001261 out:
1262 free_trash_chunk(msg);
1263 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265 error:
1266 ret = TCPCHK_EVAL_STOP;
1267 check->code = err;
1268 msg = alloc_trash_chunk();
1269 if (msg)
1270 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1271 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1272 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001273
Christopher Faulet61cc8522020-04-20 14:54:42 +02001274 wait_more_data:
1275 ret = TCPCHK_EVAL_WAIT;
1276 goto out;
1277}
Simon Horman98637e52014-06-20 12:30:16 +09001278
Christopher Faulet61cc8522020-04-20 14:54:42 +02001279/* Custom tcp-check expect function to parse and validate the MySQL initial
1280 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1281 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1282 * error occurred.
1283 */
1284static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1285{
1286 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1287}
Simon Horman98637e52014-06-20 12:30:16 +09001288
Christopher Faulet61cc8522020-04-20 14:54:42 +02001289/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1290 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1291 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1292 * an error occurred.
1293 */
1294static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1295{
1296 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001297
Christopher Faulet61cc8522020-04-20 14:54:42 +02001298 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1299 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1300 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001301
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1303}
Simon Horman98637e52014-06-20 12:30:16 +09001304
Christopher Faulet61cc8522020-04-20 14:54:42 +02001305/* Custom tcp-check expect function to parse and validate the LDAP bind response
1306 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1307 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1308 * error occurred.
1309 */
1310static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1311{
1312 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1313 enum healthcheck_status status;
1314 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001315 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001316 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001317
Christopher Faulet61cc8522020-04-20 14:54:42 +02001318 /* Check if the server speaks LDAP (ASN.1/BER)
1319 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1320 * http://tools.ietf.org/html/rfc4511
1321 */
1322 /* size of LDAPMessage */
1323 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001324
Christopher Faulet61cc8522020-04-20 14:54:42 +02001325 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1326 * messageID: 0x02 0x01 0x01: INTEGER 1
1327 * protocolOp: 0x61: bindResponse
1328 */
1329 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1330 status = HCHK_STATUS_L7RSP;
1331 desc = ist("Not LDAPv3 protocol");
1332 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001333 }
Simon Horman98637e52014-06-20 12:30:16 +09001334
Christopher Faulet61cc8522020-04-20 14:54:42 +02001335 /* size of bindResponse */
1336 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001337
Christopher Faulet61cc8522020-04-20 14:54:42 +02001338 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1339 * ldapResult: 0x0a 0x01: ENUMERATION
1340 */
1341 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1342 status = HCHK_STATUS_L7RSP;
1343 desc = ist("Not LDAPv3 protocol");
1344 goto error;
1345 }
Simon Horman98637e52014-06-20 12:30:16 +09001346
Christopher Faulet61cc8522020-04-20 14:54:42 +02001347 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1348 * resultCode
1349 */
1350 check->code = *(b_head(&check->bi) + msglen + 9);
1351 if (check->code) {
1352 status = HCHK_STATUS_L7STS;
1353 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1354 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001355 }
1356
Christopher Faulet61cc8522020-04-20 14:54:42 +02001357 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001358
Christopher Faulet61cc8522020-04-20 14:54:42 +02001359 out:
1360 free_trash_chunk(msg);
1361 return ret;
1362
1363 error:
1364 ret = TCPCHK_EVAL_STOP;
1365 msg = alloc_trash_chunk();
1366 if (msg)
1367 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1368 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1369 goto out;
1370
1371 wait_more_data:
1372 ret = TCPCHK_EVAL_WAIT;
1373 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001374}
1375
Christopher Faulet61cc8522020-04-20 14:54:42 +02001376/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1377 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1378 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001379 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001380static 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 +02001381{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001382 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1383 enum healthcheck_status status;
1384 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001385 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001387
Willy Tarreaubaaee002006-06-26 02:48:02 +02001388
Christopher Faulet61cc8522020-04-20 14:54:42 +02001389 memcpy(&framesz, b_head(&check->bi), 4);
1390 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001391
Christopher Faulet61cc8522020-04-20 14:54:42 +02001392 if (!last_read && b_data(&check->bi) < (4+framesz))
1393 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001394
Christopher Faulet61cc8522020-04-20 14:54:42 +02001395 memset(b_orig(&trash), 0, b_size(&trash));
1396 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1397 status = HCHK_STATUS_L7RSP;
1398 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1399 goto error;
1400 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001401
Christopher Faulet61cc8522020-04-20 14:54:42 +02001402 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001403
Christopher Faulet61cc8522020-04-20 14:54:42 +02001404 out:
1405 free_trash_chunk(msg);
1406 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001407
Christopher Faulet61cc8522020-04-20 14:54:42 +02001408 error:
1409 ret = TCPCHK_EVAL_STOP;
1410 msg = alloc_trash_chunk();
1411 if (msg)
1412 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1413 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1414 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001415
Christopher Faulet61cc8522020-04-20 14:54:42 +02001416 wait_more_data:
1417 ret = TCPCHK_EVAL_WAIT;
1418 goto out;
1419}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001420
Christopher Faulet61cc8522020-04-20 14:54:42 +02001421/* Custom tcp-check expect function to parse and validate the agent-check
1422 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1423 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1424 */
1425static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1426{
1427 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1428 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1429 const char *hs = NULL; /* health status */
1430 const char *as = NULL; /* admin status */
1431 const char *ps = NULL; /* performance status */
1432 const char *cs = NULL; /* maxconn */
1433 const char *err = NULL; /* first error to report */
1434 const char *wrn = NULL; /* first warning to report */
1435 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001436
Christopher Faulet61cc8522020-04-20 14:54:42 +02001437 /* We're getting an agent check response. The agent could
1438 * have been disabled in the mean time with a long check
1439 * still pending. It is important that we ignore the whole
1440 * response.
1441 */
1442 if (!(check->state & CHK_ST_ENABLED))
1443 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001444
Christopher Faulet61cc8522020-04-20 14:54:42 +02001445 /* The agent supports strings made of a single line ended by the
1446 * first CR ('\r') or LF ('\n'). This line is composed of words
1447 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1448 * line may optionally contained a description of a state change
1449 * after a sharp ('#'), which is only considered if a health state
1450 * is announced.
1451 *
1452 * Words may be composed of :
1453 * - a numeric weight suffixed by the percent character ('%').
1454 * - a health status among "up", "down", "stopped", and "fail".
1455 * - an admin status among "ready", "drain", "maint".
1456 *
1457 * These words may appear in any order. If multiple words of the
1458 * same category appear, the last one wins.
1459 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001460
Christopher Faulet61cc8522020-04-20 14:54:42 +02001461 p = b_head(&check->bi);
1462 while (*p && *p != '\n' && *p != '\r')
1463 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001464
Christopher Faulet61cc8522020-04-20 14:54:42 +02001465 if (!*p) {
1466 if (!last_read)
1467 goto wait_more_data;
1468
1469 /* at least inform the admin that the agent is mis-behaving */
1470 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1471 goto out;
1472 }
1473
1474 *p = 0;
1475 cmd = b_head(&check->bi);
1476
1477 while (*cmd) {
1478 /* look for next word */
1479 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1480 cmd++;
1481 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001482 }
1483
Christopher Faulet61cc8522020-04-20 14:54:42 +02001484 if (*cmd == '#') {
1485 /* this is the beginning of a health status description,
1486 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001487 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001488 cmd++;
1489 while (*cmd == '\t' || *cmd == ' ')
1490 cmd++;
1491 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001492 }
1493
Christopher Faulet61cc8522020-04-20 14:54:42 +02001494 /* find the end of the word so that we have a null-terminated
1495 * word between <cmd> and <p>.
1496 */
1497 p = cmd + 1;
1498 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1499 p++;
1500 if (*p)
1501 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001502
Christopher Faulet61cc8522020-04-20 14:54:42 +02001503 /* first, health statuses */
1504 if (strcasecmp(cmd, "up") == 0) {
1505 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1506 status = HCHK_STATUS_L7OKD;
1507 hs = cmd;
1508 }
1509 else if (strcasecmp(cmd, "down") == 0) {
1510 check->server->check.health = 0;
1511 status = HCHK_STATUS_L7STS;
1512 hs = cmd;
1513 }
1514 else if (strcasecmp(cmd, "stopped") == 0) {
1515 check->server->check.health = 0;
1516 status = HCHK_STATUS_L7STS;
1517 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001518 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001519 else if (strcasecmp(cmd, "fail") == 0) {
1520 check->server->check.health = 0;
1521 status = HCHK_STATUS_L7STS;
1522 hs = cmd;
1523 }
1524 /* admin statuses */
1525 else if (strcasecmp(cmd, "ready") == 0) {
1526 as = cmd;
1527 }
1528 else if (strcasecmp(cmd, "drain") == 0) {
1529 as = cmd;
1530 }
1531 else if (strcasecmp(cmd, "maint") == 0) {
1532 as = cmd;
1533 }
1534 /* try to parse a weight here and keep the last one */
1535 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1536 ps = cmd;
1537 }
1538 /* try to parse a maxconn here */
1539 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1540 cs = cmd;
1541 }
1542 else {
1543 /* keep a copy of the first error */
1544 if (!err)
1545 err = cmd;
1546 }
1547 /* skip to next word */
1548 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001549 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001550 /* here, cmd points either to \0 or to the beginning of a
1551 * description. Skip possible leading spaces.
1552 */
1553 while (*cmd == ' ' || *cmd == '\n')
1554 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001555
Christopher Faulet61cc8522020-04-20 14:54:42 +02001556 /* First, update the admin status so that we avoid sending other
1557 * possibly useless warnings and can also update the health if
1558 * present after going back up.
1559 */
1560 if (as) {
1561 if (strcasecmp(as, "drain") == 0)
1562 srv_adm_set_drain(check->server);
1563 else if (strcasecmp(as, "maint") == 0)
1564 srv_adm_set_maint(check->server);
1565 else
1566 srv_adm_set_ready(check->server);
1567 }
Simon Horman98637e52014-06-20 12:30:16 +09001568
Christopher Faulet61cc8522020-04-20 14:54:42 +02001569 /* now change weights */
1570 if (ps) {
1571 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001572
Christopher Faulet61cc8522020-04-20 14:54:42 +02001573 msg = server_parse_weight_change_request(check->server, ps);
1574 if (!wrn || !*wrn)
1575 wrn = msg;
1576 }
Simon Horman98637e52014-06-20 12:30:16 +09001577
Christopher Faulet61cc8522020-04-20 14:54:42 +02001578 if (cs) {
1579 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001580
Christopher Faulet61cc8522020-04-20 14:54:42 +02001581 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001582
Christopher Faulet61cc8522020-04-20 14:54:42 +02001583 msg = server_parse_maxconn_change_request(check->server, cs);
1584 if (!wrn || !*wrn)
1585 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001586 }
1587
Christopher Faulet61cc8522020-04-20 14:54:42 +02001588 /* and finally health status */
1589 if (hs) {
1590 /* We'll report some of the warnings and errors we have
1591 * here. Down reports are critical, we leave them untouched.
1592 * Lack of report, or report of 'UP' leaves the room for
1593 * ERR first, then WARN.
1594 */
1595 const char *msg = cmd;
1596 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001597
Christopher Faulet61cc8522020-04-20 14:54:42 +02001598 if (!*msg || status == HCHK_STATUS_L7OKD) {
1599 if (err && *err)
1600 msg = err;
1601 else if (wrn && *wrn)
1602 msg = wrn;
1603 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001604
Christopher Faulet61cc8522020-04-20 14:54:42 +02001605 t = get_trash_chunk();
1606 chunk_printf(t, "via agent : %s%s%s%s",
1607 hs, *msg ? " (" : "",
1608 msg, *msg ? ")" : "");
1609 set_server_check_status(check, status, t->area);
1610 }
1611 else if (err && *err) {
1612 /* No status change but we'd like to report something odd.
1613 * Just report the current state and copy the message.
1614 */
1615 chunk_printf(&trash, "agent reports an error : %s", err);
1616 set_server_check_status(check, status/*check->status*/, trash.area);
1617 }
1618 else if (wrn && *wrn) {
1619 /* No status change but we'd like to report something odd.
1620 * Just report the current state and copy the message.
1621 */
1622 chunk_printf(&trash, "agent warns : %s", wrn);
1623 set_server_check_status(check, status/*check->status*/, trash.area);
1624 }
1625 else
1626 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001627
Christopher Faulet61cc8522020-04-20 14:54:42 +02001628 out:
1629 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001630
Christopher Faulet61cc8522020-04-20 14:54:42 +02001631 wait_more_data:
1632 ret = TCPCHK_EVAL_WAIT;
1633 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001634}
1635
Christopher Faulet61cc8522020-04-20 14:54:42 +02001636/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1637 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1638 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001639 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001640static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001641{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001642 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1643 struct tcpcheck_connect *connect = &rule->connect;
1644 struct proxy *proxy = check->proxy;
1645 struct server *s = check->server;
1646 struct task *t = check->task;
1647 struct conn_stream *cs;
1648 struct connection *conn = NULL;
1649 struct protocol *proto;
1650 struct xprt_ops *xprt;
1651 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001652
Christopher Faulet61cc8522020-04-20 14:54:42 +02001653 /* For a connect action we'll create a new connection. We may also have
1654 * to kill a previous one. But we don't want to leave *without* a
1655 * connection if we came here from the connection layer, hence with a
1656 * connection. Thus we'll proceed in the following order :
1657 * 1: close but not release previous connection (handled by the caller)
1658 * 2: try to get a new connection
1659 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001660 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001661
Christopher Faulet61cc8522020-04-20 14:54:42 +02001662 /* 2- prepare new connection */
1663 cs = cs_new(NULL);
1664 if (!cs) {
1665 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1666 tcpcheck_get_step_id(check, rule));
1667 if (rule->comment)
1668 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1669 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1670 ret = TCPCHK_EVAL_STOP;
1671 goto out;
1672 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001673
Christopher Faulet61cc8522020-04-20 14:54:42 +02001674 /* 3- release and replace the old one on success */
1675 if (check->cs) {
1676 if (check->wait_list.events)
1677 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
1678 check->wait_list.events, &check->wait_list);
1679
1680 /* We may have been scheduled to run, and the I/O handler
1681 * expects to have a cs, so remove the tasklet
1682 */
1683 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1684 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001685 }
1686
Christopher Faulet61cc8522020-04-20 14:54:42 +02001687 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001688
Christopher Faulet61cc8522020-04-20 14:54:42 +02001689 check->cs = cs;
1690 conn = cs->conn;
1691 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001692
Christopher Faulet61cc8522020-04-20 14:54:42 +02001693 /* Maybe there were an older connection we were waiting on */
1694 check->wait_list.events = 0;
1695 conn->target = s ? &s->obj_type : &proxy->obj_type;
1696
1697 /* no client address */
1698 if (!sockaddr_alloc(&conn->dst)) {
1699 status = SF_ERR_RESOURCE;
1700 goto fail_check;
1701 }
1702
1703 /* connect to the connect rule addr if specified, otherwise the check
1704 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001705 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001706 *conn->dst = (is_addr(&connect->addr)
1707 ? connect->addr
1708 : (is_addr(&check->addr) ? check->addr : s->addr));
1709 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001710
Christopher Faulet61cc8522020-04-20 14:54:42 +02001711 port = 0;
1712 if (!port && connect->port)
1713 port = connect->port;
1714 if (!port && connect->port_expr) {
1715 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001716
Christopher Faulet61cc8522020-04-20 14:54:42 +02001717 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1718 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1719 connect->port_expr, SMP_T_SINT);
1720 if (smp)
1721 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001722 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001723 if (!port && is_inet_addr(&connect->addr))
1724 port = get_host_port(&connect->addr);
1725 if (!port && check->port)
1726 port = check->port;
1727 if (!port && is_inet_addr(&check->addr))
1728 port = get_host_port(&check->addr);
1729 if (!port)
1730 port = s->svc_port;
1731 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001732
Christopher Faulet61cc8522020-04-20 14:54:42 +02001733 xprt = ((connect->options & TCPCHK_OPT_SSL)
1734 ? xprt_get(XPRT_SSL)
1735 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001736
Christopher Faulet61cc8522020-04-20 14:54:42 +02001737 conn_prepare(conn, proto, xprt);
1738 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001739
Christopher Faulet61cc8522020-04-20 14:54:42 +02001740 status = SF_ERR_INTERNAL;
1741 if (proto && proto->connect) {
1742 struct tcpcheck_rule *next;
1743 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001744
Christopher Faulet61cc8522020-04-20 14:54:42 +02001745 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1746 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001747
Christopher Faulet61cc8522020-04-20 14:54:42 +02001748 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1749 if (!next || next->action != TCPCHK_ACT_EXPECT)
1750 flags |= CONNECT_DELACK_ALWAYS;
1751 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001752 }
1753
Christopher Faulet61cc8522020-04-20 14:54:42 +02001754 if (status != SF_ERR_NONE)
1755 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001756
Christopher Faulet61cc8522020-04-20 14:54:42 +02001757 conn->flags |= CO_FL_PRIVATE;
1758 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001759
Christopher Faulet61cc8522020-04-20 14:54:42 +02001760 /* The mux may be initialized now if there isn't server attached to the
1761 * check (email alerts) or if there is a mux proto specified or if there
1762 * is no alpn.
1763 */
1764 if (!s || connect->mux_proto || check->mux_proto || (!connect->alpn && !check->alpn_str)) {
1765 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001766
Christopher Faulet61cc8522020-04-20 14:54:42 +02001767 if (connect->mux_proto)
1768 mux_ops = connect->mux_proto->mux;
1769 else if (check->mux_proto)
1770 mux_ops = check->mux_proto->mux;
1771 else {
1772 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1773 ? PROTO_MODE_HTTP
1774 : PROTO_MODE_TCP);
1775
1776 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001777 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001778 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1779 status = SF_ERR_INTERNAL;
1780 goto fail_check;
1781 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001782 }
1783
Christopher Faulet61cc8522020-04-20 14:54:42 +02001784#ifdef USE_OPENSSL
1785 if (connect->sni)
1786 ssl_sock_set_servername(conn, connect->sni);
1787 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1788 ssl_sock_set_servername(conn, s->check.sni);
1789
1790 if (connect->alpn)
1791 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1792 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1793 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1794#endif
1795 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1796 conn->send_proxy_ofs = 1;
1797 conn->flags |= CO_FL_SOCKS4;
1798 }
1799 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1800 conn->send_proxy_ofs = 1;
1801 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001802 }
1803
Christopher Faulet61cc8522020-04-20 14:54:42 +02001804 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1805 conn->send_proxy_ofs = 1;
1806 conn->flags |= CO_FL_SEND_PROXY;
1807 }
1808 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1809 conn->send_proxy_ofs = 1;
1810 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001811 }
1812
Christopher Faulet61cc8522020-04-20 14:54:42 +02001813 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1814 /* Some servers don't like reset on close */
1815 fdtab[cs->conn->handle.fd].linger_risk = 0;
1816 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001817
Christopher Faulet61cc8522020-04-20 14:54:42 +02001818 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1819 if (xprt_add_hs(conn) < 0)
1820 status = SF_ERR_RESOURCE;
1821 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001822
Christopher Faulet61cc8522020-04-20 14:54:42 +02001823 fail_check:
1824 /* It can return one of :
1825 * - SF_ERR_NONE if everything's OK
1826 * - SF_ERR_SRVTO if there are no more servers
1827 * - SF_ERR_SRVCL if the connection was refused by the server
1828 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1829 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1830 * - SF_ERR_INTERNAL for any other purely internal errors
1831 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1832 * Note that we try to prevent the network stack from sending the ACK during the
1833 * connect() when a pure TCP check is used (without PROXY protocol).
1834 */
1835 switch (status) {
1836 case SF_ERR_NONE:
1837 /* we allow up to min(inter, timeout.connect) for a connection
1838 * to establish but only when timeout.check is set as it may be
1839 * to short for a full check otherwise
1840 */
1841 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001842
Christopher Faulet61cc8522020-04-20 14:54:42 +02001843 if (proxy->timeout.check && proxy->timeout.connect) {
1844 int t_con = tick_add(now_ms, proxy->timeout.connect);
1845 t->expire = tick_first(t->expire, t_con);
1846 }
1847 break;
1848 case SF_ERR_SRVTO: /* ETIMEDOUT */
1849 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1850 case SF_ERR_PRXCOND:
1851 case SF_ERR_RESOURCE:
1852 case SF_ERR_INTERNAL:
1853 chk_report_conn_err(check, errno, 0);
1854 ret = TCPCHK_EVAL_STOP;
1855 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001856 }
1857
Christopher Faulet61cc8522020-04-20 14:54:42 +02001858 /* don't do anything until the connection is established */
1859 if (conn->flags & CO_FL_WAIT_XPRT) {
1860 ret = TCPCHK_EVAL_WAIT;
1861 goto out;
1862 }
1863
1864 out:
1865 if (conn && check->result == CHK_RES_FAILED)
1866 conn->flags |= CO_FL_ERROR;
1867 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001868}
1869
Christopher Faulet61cc8522020-04-20 14:54:42 +02001870/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1871 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1872 * TCPCHK_EVAL_STOP if an error occurred.
1873 */
1874static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001875{
1876 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001877 struct tcpcheck_send *send = &rule->send;
1878 struct conn_stream *cs = check->cs;
1879 struct connection *conn = cs_conn(cs);
1880 struct buffer *tmp = NULL;
1881 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001882
Christopher Faulet61cc8522020-04-20 14:54:42 +02001883 /* reset the read & write buffer */
1884 b_reset(&check->bi);
1885 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001886
Christopher Faulet61cc8522020-04-20 14:54:42 +02001887 switch (send->type) {
1888 case TCPCHK_SEND_STRING:
1889 case TCPCHK_SEND_BINARY:
1890 if (istlen(send->data) >= b_size(&check->bo)) {
1891 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1892 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1893 tcpcheck_get_step_id(check, rule));
1894 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1895 ret = TCPCHK_EVAL_STOP;
1896 goto out;
1897 }
1898 b_putist(&check->bo, send->data);
1899 break;
1900 case TCPCHK_SEND_STRING_LF:
1901 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1902 if (!b_data(&check->bo))
1903 goto out;
1904 break;
1905 case TCPCHK_SEND_BINARY_LF:
1906 tmp = alloc_trash_chunk();
1907 if (!tmp)
1908 goto error_lf;
1909 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1910 if (!b_data(tmp))
1911 goto out;
1912 tmp->area[tmp->data] = '\0';
1913 b_set_data(&check->bo, b_size(&check->bo));
1914 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1915 goto error_lf;
1916 break;
1917 case TCPCHK_SEND_HTTP: {
1918 struct htx_sl *sl;
1919 struct ist meth, uri, vsn, clen, body;
1920 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001921
Christopher Faulet61cc8522020-04-20 14:54:42 +02001922 tmp = alloc_trash_chunk();
1923 if (!tmp)
1924 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001925
Christopher Faulet61cc8522020-04-20 14:54:42 +02001926 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1927 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1928 : http_known_methods[send->http.meth.meth]);
1929 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1930 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001931
Christopher Faulet61cc8522020-04-20 14:54:42 +02001932 if (istlen(vsn) == 8 &&
1933 (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))
1934 slflags |= HTX_SL_F_VER_11;
1935 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1936 if (!isttest(send->http.body))
1937 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001938
Christopher Faulet61cc8522020-04-20 14:54:42 +02001939 htx = htx_from_buf(&check->bo);
1940 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1941 if (!sl)
1942 goto error_htx;
1943 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001944
Christopher Faulet61cc8522020-04-20 14:54:42 +02001945 body = send->http.body; // TODO: handle body_fmt
1946 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001947
Christopher Faulet61cc8522020-04-20 14:54:42 +02001948 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1949 !htx_add_header(htx, ist("Content-length"), clen))
1950 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001951
Christopher Faulet61cc8522020-04-20 14:54:42 +02001952 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1953 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001954
Christopher Faulet61cc8522020-04-20 14:54:42 +02001955 list_for_each_entry(hdr, &send->http.hdrs, list) {
1956 chunk_reset(tmp);
1957 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1958 if (!b_data(tmp))
1959 continue;
1960 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1961 goto error_htx;
1962 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001963
Christopher Faulet61cc8522020-04-20 14:54:42 +02001964 }
1965 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1966 chunk_reset(tmp);
1967 httpchk_build_status_header(check->server, tmp);
1968 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1969 goto error_htx;
1970 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001971
Christopher Faulet61cc8522020-04-20 14:54:42 +02001972 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1973 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1974 !htx_add_endof(htx, HTX_BLK_EOM))
1975 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001976
Christopher Faulet61cc8522020-04-20 14:54:42 +02001977 htx_to_buf(htx, &check->bo);
1978 break;
1979 }
1980 case TCPCHK_SEND_UNDEF:
1981 /* Should never happen. */
1982 ret = TCPCHK_EVAL_STOP;
1983 goto out;
1984 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001985
Christopher Faulet6d471212020-04-22 11:09:25 +02001986
1987 if (conn->mux->snd_buf(cs, &check->bo,
1988 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001989 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001990 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001991 goto out;
1992 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001993 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001994 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001995 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1996 ret = TCPCHK_EVAL_WAIT;
1997 goto out;
1998 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001999
Christopher Faulet61cc8522020-04-20 14:54:42 +02002000 out:
2001 free_trash_chunk(tmp);
2002 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002003
Christopher Faulet61cc8522020-04-20 14:54:42 +02002004 error_htx:
2005 if (htx) {
2006 htx_reset(htx);
2007 htx_to_buf(htx, &check->bo);
2008 }
2009 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
2010 tcpcheck_get_step_id(check, rule));
2011 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2012 ret = TCPCHK_EVAL_STOP;
2013 goto out;
2014
2015 error_lf:
2016 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2017 tcpcheck_get_step_id(check, rule));
2018 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2019 ret = TCPCHK_EVAL_STOP;
2020 goto out;
2021
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002022}
2023
Christopher Faulet61cc8522020-04-20 14:54:42 +02002024/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2025 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2026 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2027 * TCPCHK_EVAL_STOP if an error occurred.
2028 */
2029static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 struct conn_stream *cs = check->cs;
2032 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002033 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002034 size_t max, read, cur_read = 0;
2035 int is_empty;
2036 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002037
Christopher Faulet61cc8522020-04-20 14:54:42 +02002038 if (check->wait_list.events & SUB_RETRY_RECV)
2039 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002040
Christopher Faulet61cc8522020-04-20 14:54:42 +02002041 if (cs->flags & CS_FL_EOS)
2042 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002043
Christopher Faulet61cc8522020-04-20 14:54:42 +02002044 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002045
Christopher Faulet61cc8522020-04-20 14:54:42 +02002046 /* prepare to detect if the mux needs more room */
2047 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002048
Christopher Faulet61cc8522020-04-20 14:54:42 +02002049 while ((cs->flags & CS_FL_RCV_MORE) ||
2050 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2051 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2052 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2053 cur_read += read;
2054 if (!read ||
2055 (cs->flags & CS_FL_WANT_ROOM) ||
2056 (--read_poll <= 0) ||
2057 (read < max && read >= global.tune.recv_enough))
2058 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002059 }
2060
Christopher Faulet61cc8522020-04-20 14:54:42 +02002061 end_recv:
2062 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2063 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2064 /* Report network errors only if we got no other data. Otherwise
2065 * we'll let the upper layers decide whether the response is OK
2066 * or not. It is very common that an RST sent by the server is
2067 * reported as an error just after the last data chunk.
2068 */
2069 goto stop;
2070 }
2071 if (!cur_read) {
2072 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2073 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2074 goto wait_more_data;
2075 }
2076 if (is_empty) {
2077 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2078 tcpcheck_get_step_id(check, rule));
2079 if (rule->comment)
2080 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2081 set_server_check_status(check, rule->expect.err_status, trash.area);
2082 goto stop;
2083 }
2084 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002085
2086 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002087 return ret;
2088
Christopher Faulet61cc8522020-04-20 14:54:42 +02002089 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002090 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002091 goto out;
2092
2093 wait_more_data:
2094 ret = TCPCHK_EVAL_WAIT;
2095 goto out;
2096}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002097
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2099 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2100 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2101 * error occurred.
2102 */
2103static 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 +02002104{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002105 struct htx *htx = htxbuf(&check->bi);
2106 struct htx_sl *sl;
2107 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002108 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002109 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002110 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002112 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002113 int match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002114
Christopher Faulet61cc8522020-04-20 14:54:42 +02002115 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002116
Christopher Faulet61cc8522020-04-20 14:54:42 +02002117 if (htx->flags & HTX_FL_PARSING_ERROR) {
2118 status = HCHK_STATUS_L7RSP;
2119 goto error;
2120 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002121
Christopher Faulet61cc8522020-04-20 14:54:42 +02002122 if (htx_is_empty(htx)) {
2123 if (last_read) {
2124 status = HCHK_STATUS_L7RSP;
2125 goto error;
2126 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002127 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002128 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002129
Christopher Faulet61cc8522020-04-20 14:54:42 +02002130 sl = http_get_stline(htx);
2131 check->code = sl->info.res.status;
2132
2133 if (check->server &&
2134 (check->server->proxy->options & PR_O_DISABLE404) &&
2135 (check->server->next_state != SRV_ST_STOPPED) &&
2136 (check->code == 404)) {
2137 /* 404 may be accepted as "stopping" only if the server was up */
2138 goto out;
2139 }
2140
2141 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2142 /* Make GCC happy ; initialize match to a failure state. */
2143 match = inverse;
2144
2145 switch (expect->type) {
2146 case TCPCHK_EXPECT_HTTP_STATUS:
2147 match = isteq(htx_sl_res_code(sl), expect->data);
2148
2149 /* Set status and description in case of error */
2150 status = HCHK_STATUS_L7STS;
2151 desc = htx_sl_res_reason(sl);
2152 break;
2153 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2154 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2155
2156 /* Set status and description in case of error */
2157 status = HCHK_STATUS_L7STS;
2158 desc = htx_sl_res_reason(sl);
2159 break;
2160
2161 case TCPCHK_EXPECT_HTTP_BODY:
2162 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2163 chunk_reset(&trash);
2164 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2165 enum htx_blk_type type = htx_get_blk_type(blk);
2166
2167 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2168 break;
2169 if (type == HTX_BLK_DATA) {
2170 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2171 break;
2172 }
2173 }
2174
2175 if (!b_data(&trash)) {
2176 if (!last_read)
2177 goto wait_more_data;
2178 status = HCHK_STATUS_L7RSP;
2179 desc = ist("HTTP content check could not find a response body");
2180 goto error;
2181 }
2182
2183 if (!last_read &&
2184 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2185 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2186 ret = TCPCHK_EVAL_WAIT;
2187 goto out;
2188 }
2189
2190 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002191 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002192 else
2193 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2194
2195 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002196 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002197 desc = (inverse
2198 ? ist("HTTP check matched unwanted content")
2199 : ist("HTTP content check did not match"));
2200 break;
2201
2202 default:
2203 /* should never happen */
2204 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002205 goto error;
2206 }
2207
Christopher Faulet61cc8522020-04-20 14:54:42 +02002208 /* Wait for more data on mismatch only if no minimum is defined (-1),
2209 * otherwise the absence of match is already conclusive.
2210 */
2211 if (!match && !last_read && (expect->min_recv == -1)) {
2212 ret = TCPCHK_EVAL_WAIT;
2213 goto out;
2214 }
2215
2216 if (!(match ^ inverse))
2217 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002218
2219 out:
2220 free_trash_chunk(msg);
2221 return ret;
2222
2223 error:
2224 ret = TCPCHK_EVAL_STOP;
2225 msg = alloc_trash_chunk();
2226 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002227 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002228 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2229 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002230
2231 wait_more_data:
2232 ret = TCPCHK_EVAL_WAIT;
2233 goto out;
2234}
2235
Christopher Faulet61cc8522020-04-20 14:54:42 +02002236/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2237 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2238 * if an error occurred.
2239 */
2240static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2241{
2242 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2243 struct tcpcheck_expect *expect = &rule->expect;
2244 struct buffer *msg = NULL;
2245 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002246
Christopher Faulet61cc8522020-04-20 14:54:42 +02002247 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002248
Christopher Faulet61cc8522020-04-20 14:54:42 +02002249 /* The current expect might need more data than the previous one, check again
2250 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002251 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002252 if (!last_read) {
2253 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2254 (b_data(&check->bi) < istlen(expect->data))) {
2255 ret = TCPCHK_EVAL_WAIT;
2256 goto out;
2257 }
2258 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2259 ret = TCPCHK_EVAL_WAIT;
2260 goto out;
2261 }
2262 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002263
Christopher Faulet61cc8522020-04-20 14:54:42 +02002264 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2265 /* Make GCC happy ; initialize match to a failure state. */
2266 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002267
Christopher Faulet61cc8522020-04-20 14:54:42 +02002268 switch (expect->type) {
2269 case TCPCHK_EXPECT_STRING:
2270 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002271 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 +02002272 break;
2273 case TCPCHK_EXPECT_REGEX:
2274 if (expect->flags & TCPCHK_EXPT_FL_CAP)
2275 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
2276 MAX_MATCH, pmatch, 0);
2277 else
2278 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2279 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002280
Christopher Faulet61cc8522020-04-20 14:54:42 +02002281 case TCPCHK_EXPECT_REGEX_BINARY:
2282 chunk_reset(&trash);
2283 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2284 if (expect->flags & TCPCHK_EXPT_FL_CAP)
2285 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
2286 MAX_MATCH, pmatch, 0);
2287 else
2288 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2289 break;
2290 case TCPCHK_EXPECT_CUSTOM:
2291 if (expect->custom)
2292 ret = expect->custom(check, rule, last_read);
2293 goto out;
2294 default:
2295 /* Should never happen. */
2296 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002297 goto out;
2298 }
2299
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002300
Christopher Faulet61cc8522020-04-20 14:54:42 +02002301 /* Wait for more data on mismatch only if no minimum is defined (-1),
2302 * otherwise the absence of match is already conclusive.
2303 */
2304 if (!match && !last_read && (expect->min_recv == -1)) {
2305 ret = TCPCHK_EVAL_WAIT;
2306 goto out;
2307 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002308
Christopher Faulet61cc8522020-04-20 14:54:42 +02002309 /* Result as expected, next rule. */
2310 if (match ^ inverse)
2311 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002312
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002313
Christopher Faulet61cc8522020-04-20 14:54:42 +02002314 /* From this point on, we matched something we did not want, this is an error state. */
2315 ret = TCPCHK_EVAL_STOP;
2316 msg = alloc_trash_chunk();
2317 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002318 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002319 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2320 free_trash_chunk(msg);
2321 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002322
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323 out:
2324 return ret;
2325}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002326
Christopher Faulet61cc8522020-04-20 14:54:42 +02002327/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2328 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2329 * waits.
2330 */
2331static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2332{
2333 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2334 struct act_rule *act_rule;
2335 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002336
Christopher Faulet61cc8522020-04-20 14:54:42 +02002337 act_rule =rule->action_kw.rule;
2338 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2339 if (act_ret != ACT_RET_CONT) {
2340 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2341 tcpcheck_get_step_id(check, rule));
2342 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2343 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002344 }
2345
Christopher Faulet61cc8522020-04-20 14:54:42 +02002346 return ret;
2347}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002348
Christopher Faulet61cc8522020-04-20 14:54:42 +02002349/* Executes a tcp-check ruleset. Note that this is called both from the
2350 * connection's wake() callback and from the check scheduling task. It returns
2351 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2352 * presenting the risk of an fd replacement.
2353 *
2354 * Please do NOT place any return statement in this function and only leave
2355 * via the out_end_tcpcheck label after setting retcode.
2356 */
2357static int tcpcheck_main(struct check *check)
2358{
2359 struct tcpcheck_rule *rule;
2360 struct conn_stream *cs = check->cs;
2361 struct connection *conn = cs_conn(cs);
2362 int must_read = 1, last_read = 0;
2363 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002364
Christopher Faulet61cc8522020-04-20 14:54:42 +02002365 /* here, we know that the check is complete or that it failed */
2366 if (check->result != CHK_RES_UNKNOWN)
2367 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002368
Christopher Faulet61cc8522020-04-20 14:54:42 +02002369 /* 1- check for connection error, if any */
2370 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2371 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002372
Christopher Faulet61cc8522020-04-20 14:54:42 +02002373 /* 2- check if we are waiting for the connection establishment. It only
2374 * happens during TCPCHK_ACT_CONNECT. */
2375 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2376 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2377 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2378 if (rule->action == TCPCHK_ACT_SEND)
2379 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2380 else if (rule->action == TCPCHK_ACT_EXPECT)
2381 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2382 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002383 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002384 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002385
2386 /* 3- check for pending outgoing data. It only happens during
2387 * TCPCHK_ACT_SEND. */
2388 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2389 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002390 ret = conn->mux->snd_buf(cs, &check->bo,
2391 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002392 if (ret <= 0) {
2393 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2394 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002395 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002396 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002397 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2398 goto out;
2399 }
2400 }
2401 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002402 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002403
Christopher Faulet61cc8522020-04-20 14:54:42 +02002404 /* 4- check if a rule must be resume. It happens if check->current_step
2405 * is defined. */
2406 else if (check->current_step)
2407 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002408
Christopher Faulet61cc8522020-04-20 14:54:42 +02002409 /* 5- It is the first evaluation. We must create a session and preset
2410 * tcp-check variables */
2411 else {
2412 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002413
Christopher Faulet61cc8522020-04-20 14:54:42 +02002414 /* First evaluation, create a session */
2415 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2416 if (!check->sess) {
2417 chunk_printf(&trash, "TCPCHK error allocating check session");
2418 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2419 goto out_end_tcpcheck;
2420 }
2421 vars_init(&check->vars, SCOPE_CHECK);
2422 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002423
Christopher Faulet61cc8522020-04-20 14:54:42 +02002424 /* Preset tcp-check variables */
2425 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2426 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002427
Christopher Faulet61cc8522020-04-20 14:54:42 +02002428 memset(&smp, 0, sizeof(smp));
2429 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2430 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002431 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002432 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002433 }
2434
Christopher Faulet61cc8522020-04-20 14:54:42 +02002435 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002436
Christopher Faulet61cc8522020-04-20 14:54:42 +02002437 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2438 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002439
Christopher Faulet61cc8522020-04-20 14:54:42 +02002440 check->code = 0;
2441 switch (rule->action) {
2442 case TCPCHK_ACT_CONNECT:
2443 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002444
Christopher Faulet61cc8522020-04-20 14:54:42 +02002445 /* close but not release yet previous connection */
2446 if (check->cs) {
2447 cs_close(check->cs);
2448 retcode = -1; /* do not reuse the fd in the caller! */
2449 }
2450 eval_ret = tcpcheck_eval_connect(check, rule);
2451 must_read = 1; last_read = 0;
2452 break;
2453 case TCPCHK_ACT_SEND:
2454 check->current_step = rule;
2455 eval_ret = tcpcheck_eval_send(check, rule);
2456 must_read = 1;
2457 break;
2458 case TCPCHK_ACT_EXPECT:
2459 check->current_step = rule;
2460 if (must_read) {
2461 if (check->proxy->timeout.check)
2462 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002463
Christopher Faulet61cc8522020-04-20 14:54:42 +02002464 eval_ret = tcpcheck_eval_recv(check, rule);
2465 if (eval_ret == TCPCHK_EVAL_STOP)
2466 goto out_end_tcpcheck;
2467 else if (eval_ret == TCPCHK_EVAL_WAIT)
2468 goto out;
2469 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2470 must_read = 0;
2471 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002472
Christopher Faulet61cc8522020-04-20 14:54:42 +02002473 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2474 ? tcpcheck_eval_expect_http(check, rule, last_read)
2475 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002476
Christopher Faulet61cc8522020-04-20 14:54:42 +02002477 if (eval_ret == TCPCHK_EVAL_WAIT) {
2478 check->current_step = rule->expect.head;
2479 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2480 }
2481 break;
2482 case TCPCHK_ACT_ACTION_KW:
2483 /* Don't update the current step */
2484 eval_ret = tcpcheck_eval_action_kw(check, rule);
2485 break;
2486 default:
2487 /* Otherwise, just go to the next one and don't update
2488 * the current step
2489 */
2490 eval_ret = TCPCHK_EVAL_CONTINUE;
2491 break;
2492 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002493
Christopher Faulet61cc8522020-04-20 14:54:42 +02002494 switch (eval_ret) {
2495 case TCPCHK_EVAL_CONTINUE:
2496 break;
2497 case TCPCHK_EVAL_WAIT:
2498 goto out;
2499 case TCPCHK_EVAL_STOP:
2500 goto out_end_tcpcheck;
2501 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002502 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002503
Christopher Faulet61cc8522020-04-20 14:54:42 +02002504 /* All rules was evaluated */
2505 if (check->current_step) {
2506 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002507
Christopher Faulet61cc8522020-04-20 14:54:42 +02002508 if (rule->action == TCPCHK_ACT_EXPECT) {
2509 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002510
Christopher Faulet61cc8522020-04-20 14:54:42 +02002511 if (check->server &&
2512 (check->server->proxy->options & PR_O_DISABLE404) &&
2513 (check->server->next_state != SRV_ST_STOPPED) &&
2514 (check->code == 404)) {
2515 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2516 goto out_end_tcpcheck;
2517 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002518
Christopher Faulet61cc8522020-04-20 14:54:42 +02002519 msg = alloc_trash_chunk();
2520 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002521 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002522 set_server_check_status(check, rule->expect.ok_status,
2523 (msg ? b_head(msg) : "(tcp-check)"));
2524 free_trash_chunk(msg);
2525 }
2526 else if (rule->action == TCPCHK_ACT_CONNECT) {
2527 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2528 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002529
Christopher Faulet61cc8522020-04-20 14:54:42 +02002530 set_server_check_status(check, status, msg);
2531 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002532 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002533 else
2534 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002535
Christopher Faulet61cc8522020-04-20 14:54:42 +02002536 out_end_tcpcheck:
2537 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2538 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002539
Christopher Faulet61cc8522020-04-20 14:54:42 +02002540 /* cleanup before leaving */
2541 check->current_step = NULL;
2542 if (check->sess != NULL) {
2543 vars_prune(&check->vars, check->sess, NULL);
2544 session_free(check->sess);
2545 check->sess = NULL;
2546 }
2547 out:
2548 return retcode;
2549}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002550
Christopher Faulet14cd3162020-04-16 14:50:06 +02002551
Christopher Faulet61cc8522020-04-20 14:54:42 +02002552/**************************************************************************/
2553/************** Health-checks based on an external process ****************/
2554/**************************************************************************/
2555static struct list pid_list = LIST_HEAD_INIT(pid_list);
2556static struct pool_head *pool_head_pid_list;
2557__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002558
Christopher Faulet61cc8522020-04-20 14:54:42 +02002559struct extcheck_env {
2560 char *name; /* environment variable name */
2561 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2562};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002563
Christopher Faulet61cc8522020-04-20 14:54:42 +02002564/* environment variables memory requirement for different types of data */
2565#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2566 * such environment variables are not updatable. */
2567#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2568#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2569#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002570
Christopher Faulet61cc8522020-04-20 14:54:42 +02002571/* external checks environment variables */
2572enum {
2573 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002574
Christopher Faulet61cc8522020-04-20 14:54:42 +02002575 /* Proxy specific environment variables */
2576 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2577 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2578 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2579 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002580
Christopher Faulet61cc8522020-04-20 14:54:42 +02002581 /* Server specific environment variables */
2582 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2583 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2584 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2585 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2586 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2587 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002588
Christopher Faulet61cc8522020-04-20 14:54:42 +02002589 EXTCHK_SIZE
2590};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002591
Christopher Faulet61cc8522020-04-20 14:54:42 +02002592const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2593 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2594 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2595 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2596 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2597 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2598 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2599 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2600 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2601 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2602 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2603 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2604};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002605
Christopher Faulet61cc8522020-04-20 14:54:42 +02002606void block_sigchld(void)
2607{
2608 sigset_t set;
2609 sigemptyset(&set);
2610 sigaddset(&set, SIGCHLD);
2611 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2612}
Willy Tarreaube373152018-09-06 11:45:30 +02002613
Christopher Faulet61cc8522020-04-20 14:54:42 +02002614void unblock_sigchld(void)
2615{
2616 sigset_t set;
2617 sigemptyset(&set);
2618 sigaddset(&set, SIGCHLD);
2619 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002620}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002621
Christopher Faulet61cc8522020-04-20 14:54:42 +02002622static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002624 struct pid_list *elem;
2625 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002626
Christopher Faulet61cc8522020-04-20 14:54:42 +02002627 elem = pool_alloc(pool_head_pid_list);
2628 if (!elem)
2629 return NULL;
2630 elem->pid = pid;
2631 elem->t = t;
2632 elem->exited = 0;
2633 check->curpid = elem;
2634 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002635
Christopher Faulet61cc8522020-04-20 14:54:42 +02002636 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2637 LIST_ADD(&pid_list, &elem->list);
2638 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002639
Christopher Faulet61cc8522020-04-20 14:54:42 +02002640 return elem;
2641}
Christopher Faulete5870d82020-04-15 11:32:03 +02002642
Christopher Faulet61cc8522020-04-20 14:54:42 +02002643static void pid_list_del(struct pid_list *elem)
2644{
2645 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002646
Christopher Faulet61cc8522020-04-20 14:54:42 +02002647 if (!elem)
2648 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002649
Christopher Faulet61cc8522020-04-20 14:54:42 +02002650 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2651 LIST_DEL(&elem->list);
2652 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002653
Christopher Faulet61cc8522020-04-20 14:54:42 +02002654 if (!elem->exited)
2655 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002656
Christopher Faulet61cc8522020-04-20 14:54:42 +02002657 check = elem->t->context;
2658 check->curpid = NULL;
2659 pool_free(pool_head_pid_list, elem);
2660}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002661
Christopher Faulet61cc8522020-04-20 14:54:42 +02002662/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2663static void pid_list_expire(pid_t pid, int status)
2664{
2665 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002666
Christopher Faulet61cc8522020-04-20 14:54:42 +02002667 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2668 list_for_each_entry(elem, &pid_list, list) {
2669 if (elem->pid == pid) {
2670 elem->t->expire = now_ms;
2671 elem->status = status;
2672 elem->exited = 1;
2673 task_wakeup(elem->t, TASK_WOKEN_IO);
2674 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002675 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002676 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002677 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2678}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002679
Christopher Faulet61cc8522020-04-20 14:54:42 +02002680static void sigchld_handler(struct sig_handler *sh)
2681{
2682 pid_t pid;
2683 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002684
Christopher Faulet61cc8522020-04-20 14:54:42 +02002685 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2686 pid_list_expire(pid, status);
2687}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002688
Christopher Faulet61cc8522020-04-20 14:54:42 +02002689static int init_pid_list(void)
2690{
2691 if (pool_head_pid_list != NULL)
2692 /* Nothing to do */
2693 return 0;
2694
2695 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2696 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2697 strerror(errno));
2698 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002699 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002700
Christopher Faulet61cc8522020-04-20 14:54:42 +02002701 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2702 if (pool_head_pid_list == NULL) {
2703 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2704 strerror(errno));
2705 return 1;
2706 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002707
Christopher Faulet61cc8522020-04-20 14:54:42 +02002708 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002709}
2710
Christopher Faulet61cc8522020-04-20 14:54:42 +02002711/* helper macro to set an environment variable and jump to a specific label on failure. */
2712#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002713
Christopher Faulet61cc8522020-04-20 14:54:42 +02002714/*
2715 * helper function to allocate enough memory to store an environment variable.
2716 * It will also check that the environment variable is updatable, and silently
2717 * fail if not.
2718 */
2719static int extchk_setenv(struct check *check, int idx, const char *value)
2720{
2721 int len, ret;
2722 char *envname;
2723 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002724
Christopher Faulet61cc8522020-04-20 14:54:42 +02002725 if (idx < 0 || idx >= EXTCHK_SIZE) {
2726 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2727 return 1;
2728 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002729
Christopher Faulet61cc8522020-04-20 14:54:42 +02002730 envname = extcheck_envs[idx].name;
2731 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002732
Christopher Faulet61cc8522020-04-20 14:54:42 +02002733 /* Check if the environment variable is already set, and silently reject
2734 * the update if this one is not updatable. */
2735 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2736 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002737
Christopher Faulet61cc8522020-04-20 14:54:42 +02002738 /* Instead of sending NOT_USED, sending an empty value is preferable */
2739 if (strcmp(value, "NOT_USED") == 0) {
2740 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002741 }
2742
Christopher Faulet61cc8522020-04-20 14:54:42 +02002743 len = strlen(envname) + 1;
2744 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2745 len += strlen(value);
2746 else
2747 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002748
Christopher Faulet61cc8522020-04-20 14:54:42 +02002749 if (!check->envp[idx])
2750 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002751
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752 if (!check->envp[idx]) {
2753 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2754 return 1;
2755 }
2756 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2757 if (ret < 0) {
2758 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2759 return 1;
2760 }
2761 else if (ret > len) {
2762 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2763 return 1;
2764 }
2765 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002766}
2767
Christopher Faulet61cc8522020-04-20 14:54:42 +02002768static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002769{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002770 struct server *s = check->server;
2771 struct proxy *px = s->proxy;
2772 struct listener *listener = NULL, *l;
2773 int i;
2774 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2775 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002776
Christopher Faulet61cc8522020-04-20 14:54:42 +02002777 list_for_each_entry(l, &px->conf.listeners, by_fe)
2778 /* Use the first INET, INET6 or UNIX listener */
2779 if (l->addr.ss_family == AF_INET ||
2780 l->addr.ss_family == AF_INET6 ||
2781 l->addr.ss_family == AF_UNIX) {
2782 listener = l;
2783 break;
2784 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002785
Christopher Faulet61cc8522020-04-20 14:54:42 +02002786 check->curpid = NULL;
2787 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2788 if (!check->envp) {
2789 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2790 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002791 }
2792
Christopher Faulet61cc8522020-04-20 14:54:42 +02002793 check->argv = calloc(6, sizeof(char *));
2794 if (!check->argv) {
2795 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2796 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002797 }
2798
Christopher Faulet61cc8522020-04-20 14:54:42 +02002799 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002800
Christopher Faulet61cc8522020-04-20 14:54:42 +02002801 if (!listener) {
2802 check->argv[1] = strdup("NOT_USED");
2803 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002804 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002805 else if (listener->addr.ss_family == AF_INET ||
2806 listener->addr.ss_family == AF_INET6) {
2807 addr_to_str(&listener->addr, buf, sizeof(buf));
2808 check->argv[1] = strdup(buf);
2809 port_to_str(&listener->addr, buf, sizeof(buf));
2810 check->argv[2] = strdup(buf);
2811 }
2812 else if (listener->addr.ss_family == AF_UNIX) {
2813 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002814
Christopher Faulet61cc8522020-04-20 14:54:42 +02002815 un = (struct sockaddr_un *)&listener->addr;
2816 check->argv[1] = strdup(un->sun_path);
2817 check->argv[2] = strdup("NOT_USED");
2818 }
2819 else {
2820 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2821 goto err;
2822 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002823
Christopher Faulet61cc8522020-04-20 14:54:42 +02002824 if (!check->argv[1] || !check->argv[2]) {
2825 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2826 goto err;
2827 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002828
Christopher Faulet61cc8522020-04-20 14:54:42 +02002829 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2830 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2831 if (!check->argv[3] || !check->argv[4]) {
2832 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2833 goto err;
2834 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002835
Christopher Faulet61cc8522020-04-20 14:54:42 +02002836 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2837 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2838 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002839
Christopher Faulet61cc8522020-04-20 14:54:42 +02002840 for (i = 0; i < 5; i++) {
2841 if (!check->argv[i]) {
2842 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2843 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002844 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002845 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002846
Christopher Faulet61cc8522020-04-20 14:54:42 +02002847 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2848 /* Add proxy environment variables */
2849 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2850 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2851 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2852 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2853 /* Add server environment variables */
2854 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2855 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2856 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2857 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2858 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2859 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002860
Christopher Faulet61cc8522020-04-20 14:54:42 +02002861 /* Ensure that we don't leave any hole in check->envp */
2862 for (i = 0; i < EXTCHK_SIZE; i++)
2863 if (!check->envp[i])
2864 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002865
Christopher Faulet61cc8522020-04-20 14:54:42 +02002866 return 1;
2867err:
2868 if (check->envp) {
2869 for (i = 0; i < EXTCHK_SIZE; i++)
2870 free(check->envp[i]);
2871 free(check->envp);
2872 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002873 }
2874
Christopher Faulet61cc8522020-04-20 14:54:42 +02002875 if (check->argv) {
2876 for (i = 1; i < 5; i++)
2877 free(check->argv[i]);
2878 free(check->argv);
2879 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002880 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002881 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002882}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002883
Christopher Faulet61cc8522020-04-20 14:54:42 +02002884/*
2885 * establish a server health-check that makes use of a process.
2886 *
2887 * It can return one of :
2888 * - SF_ERR_NONE if everything's OK
2889 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2890 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2891 *
2892 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002893 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002894static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002895{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002896 char buf[256];
2897 struct check *check = t->context;
2898 struct server *s = check->server;
2899 struct proxy *px = s->proxy;
2900 int status;
2901 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002902
Christopher Faulet61cc8522020-04-20 14:54:42 +02002903 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002904
Christopher Faulet61cc8522020-04-20 14:54:42 +02002905 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002906
Christopher Faulet61cc8522020-04-20 14:54:42 +02002907 pid = fork();
2908 if (pid < 0) {
2909 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2910 (global.tune.options & GTUNE_INSECURE_FORK) ?
2911 "" : " (likely caused by missing 'insecure-fork-wanted')",
2912 strerror(errno));
2913 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002914 goto out;
2915 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002916 if (pid == 0) {
2917 /* Child */
2918 extern char **environ;
2919 struct rlimit limit;
2920 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002921
Christopher Faulet61cc8522020-04-20 14:54:42 +02002922 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2923 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002924
Christopher Faulet61cc8522020-04-20 14:54:42 +02002925 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002926
Christopher Faulet61cc8522020-04-20 14:54:42 +02002927 /* restore the initial FD limits */
2928 limit.rlim_cur = rlim_fd_cur_at_boot;
2929 limit.rlim_max = rlim_fd_max_at_boot;
2930 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2931 getrlimit(RLIMIT_NOFILE, &limit);
2932 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2933 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2934 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2935 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002936
Christopher Faulet61cc8522020-04-20 14:54:42 +02002937 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002938
Christopher Faulet61cc8522020-04-20 14:54:42 +02002939 /* Update some environment variables and command args: curconn, server addr and server port */
2940 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002941
Christopher Faulet61cc8522020-04-20 14:54:42 +02002942 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2943 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002944
Christopher Faulet61cc8522020-04-20 14:54:42 +02002945 *check->argv[4] = 0;
2946 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2947 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2948 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002949
Christopher Faulet61cc8522020-04-20 14:54:42 +02002950 haproxy_unblock_signals();
2951 execvp(px->check_command, check->argv);
2952 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2953 strerror(errno));
2954 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002955 }
2956
Christopher Faulet61cc8522020-04-20 14:54:42 +02002957 /* Parent */
2958 if (check->result == CHK_RES_UNKNOWN) {
2959 if (pid_list_add(pid, t) != NULL) {
2960 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2961
2962 if (px->timeout.check && px->timeout.connect) {
2963 int t_con = tick_add(now_ms, px->timeout.connect);
2964 t->expire = tick_first(t->expire, t_con);
2965 }
2966 status = SF_ERR_NONE;
2967 goto out;
2968 }
2969 else {
2970 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2971 }
2972 kill(pid, SIGTERM); /* process creation error */
2973 }
2974 else
2975 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2976
2977out:
2978 unblock_sigchld();
2979 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002980}
2981
Christopher Faulet61cc8522020-04-20 14:54:42 +02002982/*
2983 * manages a server health-check that uses an external process. Returns
2984 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002985 *
2986 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002987 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002988 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002989static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002990{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002991 struct check *check = context;
2992 struct server *s = check->server;
2993 int rv;
2994 int ret;
2995 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002996
Christopher Faulet61cc8522020-04-20 14:54:42 +02002997 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2998 if (!(check->state & CHK_ST_INPROGRESS)) {
2999 /* no check currently running */
3000 if (!expired) /* woke up too early */
3001 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003002
Christopher Faulet61cc8522020-04-20 14:54:42 +02003003 /* we don't send any health-checks when the proxy is
3004 * stopped, the server should not be checked or the check
3005 * is disabled.
3006 */
3007 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3008 s->proxy->state == PR_STSTOPPED)
3009 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003010
Christopher Faulet61cc8522020-04-20 14:54:42 +02003011 /* we'll initiate a new check */
3012 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003013
Christopher Faulet61cc8522020-04-20 14:54:42 +02003014 check->state |= CHK_ST_INPROGRESS;
3015
3016 ret = connect_proc_chk(t);
3017 if (ret == SF_ERR_NONE) {
3018 /* the process was forked, we allow up to min(inter,
3019 * timeout.connect) for it to report its status, but
3020 * only when timeout.check is set as it may be to short
3021 * for a full check otherwise.
3022 */
3023 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3024
3025 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3026 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3027 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003028 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003029 task_set_affinity(t, tid_bit);
3030 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003031 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003032
Christopher Faulet61cc8522020-04-20 14:54:42 +02003033 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003034
Christopher Faulet61cc8522020-04-20 14:54:42 +02003035 check->state &= ~CHK_ST_INPROGRESS;
3036 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003037
Christopher Faulet61cc8522020-04-20 14:54:42 +02003038 /* we allow up to min(inter, timeout.connect) for a connection
3039 * to establish but only when timeout.check is set
3040 * as it may be to short for a full check otherwise
3041 */
3042 while (tick_is_expired(t->expire, now_ms)) {
3043 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003044
Christopher Faulet61cc8522020-04-20 14:54:42 +02003045 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3046 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003047
Christopher Faulet61cc8522020-04-20 14:54:42 +02003048 if (s->proxy->timeout.check)
3049 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003050 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003051 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003052 else {
3053 /* there was a test running.
3054 * First, let's check whether there was an uncaught error,
3055 * which can happen on connect timeout or error.
3056 */
3057 if (check->result == CHK_RES_UNKNOWN) {
3058 /* good connection is enough for pure TCP check */
3059 struct pid_list *elem = check->curpid;
3060 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003061
Christopher Faulet61cc8522020-04-20 14:54:42 +02003062 if (elem->exited) {
3063 status = elem->status; /* Save in case the process exits between use below */
3064 if (!WIFEXITED(status))
3065 check->code = -1;
3066 else
3067 check->code = WEXITSTATUS(status);
3068 if (!WIFEXITED(status) || WEXITSTATUS(status))
3069 status = HCHK_STATUS_PROCERR;
3070 else
3071 status = HCHK_STATUS_PROCOK;
3072 } else if (expired) {
3073 status = HCHK_STATUS_PROCTOUT;
3074 ha_warning("kill %d\n", (int)elem->pid);
3075 kill(elem->pid, SIGTERM);
3076 }
3077 set_server_check_status(check, status, NULL);
3078 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003079
Christopher Faulet61cc8522020-04-20 14:54:42 +02003080 if (check->result == CHK_RES_FAILED) {
3081 /* a failure or timeout detected */
3082 check_notify_failure(check);
3083 }
3084 else if (check->result == CHK_RES_CONDPASS) {
3085 /* check is OK but asks for stopping mode */
3086 check_notify_stopping(check);
3087 }
3088 else if (check->result == CHK_RES_PASSED) {
3089 /* a success was detected */
3090 check_notify_success(check);
3091 }
3092 task_set_affinity(t, 1);
3093 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003094
Christopher Faulet61cc8522020-04-20 14:54:42 +02003095 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003096
Christopher Faulet61cc8522020-04-20 14:54:42 +02003097 rv = 0;
3098 if (global.spread_checks > 0) {
3099 rv = srv_getinter(check) * global.spread_checks / 100;
3100 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3101 }
3102 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3103 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003104
Christopher Faulet61cc8522020-04-20 14:54:42 +02003105 reschedule:
3106 while (tick_is_expired(t->expire, now_ms))
3107 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003108
Christopher Faulet61cc8522020-04-20 14:54:42 +02003109 out_unlock:
3110 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3111 return t;
3112}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003113
Baptiste Assmann248f1172018-03-01 21:49:01 +01003114
Christopher Faulet61cc8522020-04-20 14:54:42 +02003115/**************************************************************************/
3116/***************** Health-checks based on connections *********************/
3117/**************************************************************************/
3118/* This function is used only for server health-checks. It handles connection
3119 * status updates including errors. If necessary, it wakes the check task up.
3120 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3121 * connection (eg: reconnect). It relies on tcpcheck_main().
3122 */
3123static int wake_srv_chk(struct conn_stream *cs)
3124{
3125 struct connection *conn = cs->conn;
3126 struct check *check = cs->data;
3127 struct email_alertq *q = container_of(check, typeof(*q), check);
3128 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003129
Christopher Faulet61cc8522020-04-20 14:54:42 +02003130 if (check->server)
3131 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3132 else
3133 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003134
Christopher Faulet61cc8522020-04-20 14:54:42 +02003135 /* we may have to make progress on the TCP checks */
3136 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003137
Christopher Faulet61cc8522020-04-20 14:54:42 +02003138 cs = check->cs;
3139 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003140
Christopher Faulet61cc8522020-04-20 14:54:42 +02003141 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3142 /* We may get error reports bypassing the I/O handlers, typically
3143 * the case when sending a pure TCP check which fails, then the I/O
3144 * handlers above are not called. This is completely handled by the
3145 * main processing task so let's simply wake it up. If we get here,
3146 * we expect errno to still be valid.
3147 */
3148 chk_report_conn_err(check, errno, 0);
3149 task_wakeup(check->task, TASK_WOKEN_IO);
3150 }
3151
3152 if (check->result != CHK_RES_UNKNOWN) {
3153 /* Check complete or aborted. If connection not yet closed do it
3154 * now and wake the check task up to be sure the result is
3155 * handled ASAP. */
3156 conn_sock_drain(conn);
3157 cs_close(cs);
3158 ret = -1;
3159 /* We may have been scheduled to run, and the
3160 * I/O handler expects to have a cs, so remove
3161 * the tasklet
3162 */
3163 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3164 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003165 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003166
3167 if (check->server)
3168 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003169 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003170 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003171
Christopher Faulet61cc8522020-04-20 14:54:42 +02003172 /* if a connection got replaced, we must absolutely prevent the connection
3173 * handler from touching its fd, and perform the FD polling updates ourselves
3174 */
3175 if (ret < 0)
3176 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003177
Christopher Faulet61cc8522020-04-20 14:54:42 +02003178 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003179}
3180
Christopher Faulet61cc8522020-04-20 14:54:42 +02003181/* This function checks if any I/O is wanted, and if so, attempts to do so */
3182static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003183{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003184 struct check *check = ctx;
3185 struct conn_stream *cs = check->cs;
3186 struct email_alertq *q = container_of(check, typeof(*q), check);
3187 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003188
Christopher Faulet61cc8522020-04-20 14:54:42 +02003189 if (!(check->wait_list.events & SUB_RETRY_SEND))
3190 ret = wake_srv_chk(cs);
3191 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3192 if (check->server)
3193 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3194 else
3195 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003196
Christopher Faulet61cc8522020-04-20 14:54:42 +02003197 if (unlikely(check->result == CHK_RES_FAILED)) {
3198 /* collect possible new errors */
3199 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3200 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003201
Christopher Faulet61cc8522020-04-20 14:54:42 +02003202 /* Reset the check buffer... */
3203 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003204
Christopher Faulet61cc8522020-04-20 14:54:42 +02003205 /* Close the connection... We still attempt to nicely close if,
3206 * for instance, SSL needs to send a "close notify." Later, we perform
3207 * a hard close and reset the connection if some data are pending,
3208 * otherwise we end up with many TIME_WAITs and eat all the source port
3209 * range quickly. To avoid sending RSTs all the time, we first try to
3210 * drain pending data.
3211 */
3212 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3213 * connection, to make sure cs_shutw() will not lead to a shutdown()
3214 * that would provoke TIME_WAITs.
3215 */
3216 cs_shutr(cs, CS_SHR_DRAIN);
3217 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003218
Christopher Faulet61cc8522020-04-20 14:54:42 +02003219 /* OK, let's not stay here forever */
3220 if (check->result == CHK_RES_FAILED)
3221 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003222
Christopher Faulet61cc8522020-04-20 14:54:42 +02003223 task_wakeup(t, TASK_WOKEN_IO);
3224 }
3225
3226 if (check->server)
3227 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3228 else
3229 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003230 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003231 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003232}
3233
Christopher Faulet61cc8522020-04-20 14:54:42 +02003234/* manages a server health-check that uses a connection. Returns
3235 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3236 *
3237 * Please do NOT place any return statement in this function and only leave
3238 * via the out_unlock label.
3239 */
3240static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003241{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003242 struct check *check = context;
3243 struct proxy *proxy = check->proxy;
3244 struct conn_stream *cs = check->cs;
3245 struct connection *conn = cs_conn(cs);
3246 int rv;
3247 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003248
Christopher Faulet61cc8522020-04-20 14:54:42 +02003249 if (check->server)
3250 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3251 if (!(check->state & CHK_ST_INPROGRESS)) {
3252 /* no check currently running */
3253 if (!expired) /* woke up too early */
3254 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003255
Christopher Faulet61cc8522020-04-20 14:54:42 +02003256 /* we don't send any health-checks when the proxy is
3257 * stopped, the server should not be checked or the check
3258 * is disabled.
3259 */
3260 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3261 proxy->state == PR_STSTOPPED)
3262 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003263
Christopher Faulet61cc8522020-04-20 14:54:42 +02003264 /* we'll initiate a new check */
3265 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003266
Christopher Faulet61cc8522020-04-20 14:54:42 +02003267 check->state |= CHK_ST_INPROGRESS;
3268 b_reset(&check->bi);
3269 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003270
Christopher Faulet61cc8522020-04-20 14:54:42 +02003271 task_set_affinity(t, tid_bit);
3272 cs = check->cs;
3273 conn = cs_conn(cs);
3274 if (!conn) {
3275 check->current_step = NULL;
3276 tcpcheck_main(check);
3277 goto out_unlock;
3278 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003279
Christopher Faulet61cc8522020-04-20 14:54:42 +02003280 conn->flags |= CO_FL_ERROR;
3281 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003282
Christopher Faulet61cc8522020-04-20 14:54:42 +02003283 /* here, we have seen a synchronous error, no fd was allocated */
3284 task_set_affinity(t, MAX_THREADS_MASK);
3285 if (cs) {
3286 if (check->wait_list.events)
3287 cs->conn->xprt->unsubscribe(cs->conn,
3288 cs->conn->xprt_ctx,
3289 check->wait_list.events,
3290 &check->wait_list);
3291 /* We may have been scheduled to run, and the
3292 * I/O handler expects to have a cs, so remove
3293 * the tasklet
3294 */
3295 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3296 cs_destroy(cs);
3297 cs = check->cs = NULL;
3298 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003299 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003300
Christopher Faulet61cc8522020-04-20 14:54:42 +02003301 check->state &= ~CHK_ST_INPROGRESS;
3302 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003303
Christopher Faulet61cc8522020-04-20 14:54:42 +02003304 /* we allow up to min(inter, timeout.connect) for a connection
3305 * to establish but only when timeout.check is set
3306 * as it may be to short for a full check otherwise
3307 */
3308 while (tick_is_expired(t->expire, now_ms)) {
3309 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003310
Christopher Faulet61cc8522020-04-20 14:54:42 +02003311 t_con = tick_add(t->expire, proxy->timeout.connect);
3312 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3313 if (proxy->timeout.check)
3314 t->expire = tick_first(t->expire, t_con);
3315 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003316 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003317 else {
3318 /* there was a test running.
3319 * First, let's check whether there was an uncaught error,
3320 * which can happen on connect timeout or error.
3321 */
3322 if (check->result == CHK_RES_UNKNOWN) {
3323 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3324 chk_report_conn_err(check, 0, expired);
3325 }
3326 else
3327 goto out_unlock; /* timeout not reached, wait again */
3328 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003329
Christopher Faulet61cc8522020-04-20 14:54:42 +02003330 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003331
Christopher Faulet61cc8522020-04-20 14:54:42 +02003332 check->current_step = NULL;
3333 if (check->sess != NULL) {
3334 session_free(check->sess);
3335 check->sess = NULL;
3336 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003337
Christopher Faulet61cc8522020-04-20 14:54:42 +02003338 if (conn && conn->xprt) {
3339 /* The check was aborted and the connection was not yet closed.
3340 * This can happen upon timeout, or when an external event such
3341 * as a failed response coupled with "observe layer7" caused the
3342 * server state to be suddenly changed.
3343 */
3344 conn_sock_drain(conn);
3345 cs_close(cs);
3346 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003347
Christopher Faulet61cc8522020-04-20 14:54:42 +02003348 if (cs) {
3349 if (check->wait_list.events)
3350 cs->conn->xprt->unsubscribe(cs->conn,
3351 cs->conn->xprt_ctx,
3352 check->wait_list.events,
3353 &check->wait_list);
3354 /* We may have been scheduled to run, and the
3355 * I/O handler expects to have a cs, so remove
3356 * the tasklet
3357 */
3358 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3359 cs_destroy(cs);
3360 cs = check->cs = NULL;
3361 conn = NULL;
3362 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003363
Christopher Faulet61cc8522020-04-20 14:54:42 +02003364 if (check->server) {
3365 if (check->result == CHK_RES_FAILED) {
3366 /* a failure or timeout detected */
3367 check_notify_failure(check);
3368 }
3369 else if (check->result == CHK_RES_CONDPASS) {
3370 /* check is OK but asks for stopping mode */
3371 check_notify_stopping(check);
3372 }
3373 else if (check->result == CHK_RES_PASSED) {
3374 /* a success was detected */
3375 check_notify_success(check);
3376 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003377 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003378 task_set_affinity(t, MAX_THREADS_MASK);
3379 check->state &= ~CHK_ST_INPROGRESS;
3380
3381 if (check->server) {
3382 rv = 0;
3383 if (global.spread_checks > 0) {
3384 rv = srv_getinter(check) * global.spread_checks / 100;
3385 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3386 }
3387 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003388 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003389 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003390
Christopher Faulet61cc8522020-04-20 14:54:42 +02003391 reschedule:
3392 while (tick_is_expired(t->expire, now_ms))
3393 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3394 out_unlock:
3395 if (check->server)
3396 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3397 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003398}
3399
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003400
Christopher Faulet61cc8522020-04-20 14:54:42 +02003401/**************************************************************************/
3402/******************* Internals to parse tcp-check rules *******************/
3403/**************************************************************************/
3404struct action_kw_list tcp_check_keywords = {
3405 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3406};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003407
Christopher Faulet61cc8522020-04-20 14:54:42 +02003408/* Return the struct action_kw associated to a keyword */
3409static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003410{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003411 return action_lookup(&tcp_check_keywords.list, kw);
3412}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003413
Christopher Faulet61cc8522020-04-20 14:54:42 +02003414static void action_kw_tcp_check_build_list(struct buffer *chk)
3415{
3416 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003417}
3418
Christopher Faulet61cc8522020-04-20 14:54:42 +02003419/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3420 * returned on error.
3421 */
3422static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3423 struct list *rules, struct action_kw *kw,
3424 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003425{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003426 struct tcpcheck_rule *chk = NULL;
3427 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003428
Christopher Faulet61cc8522020-04-20 14:54:42 +02003429 actrule = calloc(1, sizeof(*actrule));
3430 if (!actrule) {
3431 memprintf(errmsg, "out of memory");
3432 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003433 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003434 actrule->kw = kw;
3435 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003436
Christopher Faulet61cc8522020-04-20 14:54:42 +02003437 cur_arg++;
3438 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3439 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3440 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003441 }
3442
Christopher Faulet61cc8522020-04-20 14:54:42 +02003443 chk = calloc(1, sizeof(*chk));
3444 if (!chk) {
3445 memprintf(errmsg, "out of memory");
3446 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003447 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003448 chk->action = TCPCHK_ACT_ACTION_KW;
3449 chk->action_kw.rule = actrule;
3450 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003451
3452 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003453 free(actrule);
3454 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003455}
3456
Christopher Faulet61cc8522020-04-20 14:54:42 +02003457/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3458 * returned on error.
3459 */
3460static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3461 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003462{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003463 struct tcpcheck_rule *chk = NULL;
3464 struct sockaddr_storage *sk = NULL;
3465 char *comment = NULL, *sni = NULL, *alpn = NULL;
3466 struct sample_expr *port_expr = NULL;
3467 unsigned short conn_opts = 0;
3468 long port = 0;
3469 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003470
Christopher Faulet61cc8522020-04-20 14:54:42 +02003471 list_for_each_entry(chk, rules, list) {
3472 if (chk->action == TCPCHK_ACT_CONNECT)
3473 break;
3474 if (chk->action == TCPCHK_ACT_COMMENT ||
3475 chk->action == TCPCHK_ACT_ACTION_KW ||
3476 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3477 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003478
Christopher Faulet61cc8522020-04-20 14:54:42 +02003479 memprintf(errmsg, "first step MUST also be a 'connect', "
3480 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3481 "when there is a 'connect' step in the tcp-check ruleset");
3482 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003483 }
3484
Christopher Faulet61cc8522020-04-20 14:54:42 +02003485 cur_arg++;
3486 while (*(args[cur_arg])) {
3487 if (strcmp(args[cur_arg], "default") == 0)
3488 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3489 else if (strcmp(args[cur_arg], "addr") == 0) {
3490 int port1, port2;
3491 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003492
Christopher Faulet61cc8522020-04-20 14:54:42 +02003493 if (!*(args[cur_arg+1])) {
3494 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3495 goto error;
3496 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003497
Christopher Faulet61cc8522020-04-20 14:54:42 +02003498 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3499 if (!sk) {
3500 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3501 goto error;
3502 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003503
Christopher Faulet61cc8522020-04-20 14:54:42 +02003504 proto = protocol_by_family(sk->ss_family);
3505 if (!proto || !proto->connect) {
3506 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3507 args[cur_arg]);
3508 goto error;
3509 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003510
Christopher Faulet61cc8522020-04-20 14:54:42 +02003511 if (port1 != port2) {
3512 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3513 args[cur_arg], args[cur_arg+1]);
3514 goto error;
3515 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003516
Christopher Faulet61cc8522020-04-20 14:54:42 +02003517 cur_arg++;
3518 }
3519 else if (strcmp(args[cur_arg], "port") == 0) {
3520 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003521
Christopher Faulet61cc8522020-04-20 14:54:42 +02003522 if (!*(args[cur_arg+1])) {
3523 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3524 goto error;
3525 }
3526 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003527
Christopher Faulet61cc8522020-04-20 14:54:42 +02003528 port = 0;
3529 release_sample_expr(port_expr);
3530 p = args[cur_arg]; end = p + strlen(p);
3531 port = read_uint(&p, end);
3532 if (p != end) {
3533 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003534
Christopher Faulet61cc8522020-04-20 14:54:42 +02003535 px->conf.args.ctx = ARGC_SRV;
3536 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3537 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003538
Christopher Faulet61cc8522020-04-20 14:54:42 +02003539 if (!port_expr) {
3540 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3541 goto error;
3542 }
3543 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3544 memprintf(errmsg, "error detected while parsing port expression : "
3545 " fetch method '%s' extracts information from '%s', "
3546 "none of which is available here.\n",
3547 args[cur_arg], sample_src_names(port_expr->fetch->use));
3548 goto error;
3549 }
3550 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3551 }
3552 else if (port > 65535 || port < 1) {
3553 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3554 args[cur_arg]);
3555 goto error;
3556 }
3557 }
3558 else if (strcmp(args[cur_arg], "comment") == 0) {
3559 if (!*(args[cur_arg+1])) {
3560 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3561 goto error;
3562 }
3563 cur_arg++;
3564 free(comment);
3565 comment = strdup(args[cur_arg]);
3566 if (!comment) {
3567 memprintf(errmsg, "out of memory");
3568 goto error;
3569 }
3570 }
3571 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3572 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3573 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3574 conn_opts |= TCPCHK_OPT_SOCKS4;
3575 else if (strcmp(args[cur_arg], "linger") == 0)
3576 conn_opts |= TCPCHK_OPT_LINGER;
3577#ifdef USE_OPENSSL
3578 else if (strcmp(args[cur_arg], "ssl") == 0) {
3579 px->options |= PR_O_TCPCHK_SSL;
3580 conn_opts |= TCPCHK_OPT_SSL;
3581 }
3582 else if (strcmp(args[cur_arg], "sni") == 0) {
3583 if (!*(args[cur_arg+1])) {
3584 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3585 goto error;
3586 }
3587 cur_arg++;
3588 free(sni);
3589 sni = strdup(args[cur_arg]);
3590 if (!sni) {
3591 memprintf(errmsg, "out of memory");
3592 goto error;
3593 }
3594 }
3595 else if (strcmp(args[cur_arg], "alpn") == 0) {
3596#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3597 free(alpn);
3598 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3599 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3600 goto error;
3601 }
3602 cur_arg++;
3603#else
3604 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003605 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003606#endif
3607 }
3608#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003609
Christopher Faulet61cc8522020-04-20 14:54:42 +02003610 else {
3611 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3612#ifdef USE_OPENSSL
3613 ", 'ssl', 'sni', 'alpn'"
3614#endif /* USE_OPENSSL */
3615 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3616 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003617 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003618 }
3619 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003620 }
3621
Christopher Faulet61cc8522020-04-20 14:54:42 +02003622 chk = calloc(1, sizeof(*chk));
3623 if (!chk) {
3624 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003625 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003626 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003627 chk->action = TCPCHK_ACT_CONNECT;
3628 chk->comment = comment;
3629 chk->connect.port = port;
3630 chk->connect.options = conn_opts;
3631 chk->connect.sni = sni;
3632 chk->connect.alpn = alpn;
3633 chk->connect.alpn_len= alpn_len;
3634 chk->connect.port_expr= port_expr;
3635 if (sk)
3636 chk->connect.addr = *sk;
3637 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003638
Christopher Faulet61cc8522020-04-20 14:54:42 +02003639 error:
3640 free(alpn);
3641 free(sni);
3642 free(comment);
3643 release_sample_expr(port_expr);
3644 return NULL;
3645}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003646
Christopher Faulet61cc8522020-04-20 14:54:42 +02003647/* Parses and creates a tcp-check send rule. NULL is returned on error */
3648static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3649 const char *file, int line, char **errmsg)
3650{
3651 struct tcpcheck_rule *chk = NULL;
3652 char *comment = NULL, *data = NULL;
3653 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003654
Christopher Faulet61cc8522020-04-20 14:54:42 +02003655 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3656 if (!*(args[cur_arg+1])) {
3657 memprintf(errmsg, "'%s' expects a %s as argument",
3658 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003659 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003660 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003661
Christopher Faulet61cc8522020-04-20 14:54:42 +02003662 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003663
Christopher Faulet61cc8522020-04-20 14:54:42 +02003664 cur_arg += 2;
3665 while (*(args[cur_arg])) {
3666 if (strcmp(args[cur_arg], "comment") == 0) {
3667 if (!*(args[cur_arg+1])) {
3668 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3669 goto error;
3670 }
3671 cur_arg++;
3672 free(comment);
3673 comment = strdup(args[cur_arg]);
3674 if (!comment) {
3675 memprintf(errmsg, "out of memory");
3676 goto error;
3677 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003678 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003679 else if (strcmp(args[cur_arg], "log-format") == 0) {
3680 if (type == TCPCHK_SEND_BINARY)
3681 type = TCPCHK_SEND_BINARY_LF;
3682 else if (type == TCPCHK_SEND_STRING)
3683 type = TCPCHK_SEND_STRING_LF;
3684 }
3685 else {
3686 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3687 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003688 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003689 }
3690 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003691 }
3692
Christopher Faulet61cc8522020-04-20 14:54:42 +02003693 chk = calloc(1, sizeof(*chk));
3694 if (!chk) {
3695 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003696 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003697 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003698 chk->action = TCPCHK_ACT_SEND;
3699 chk->comment = comment;
3700 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003701
Christopher Faulet61cc8522020-04-20 14:54:42 +02003702 switch (chk->send.type) {
3703 case TCPCHK_SEND_STRING:
3704 chk->send.data = ist2(strdup(data), strlen(data));
3705 if (!isttest(chk->send.data)) {
3706 memprintf(errmsg, "out of memory");
3707 goto error;
3708 }
3709 break;
3710 case TCPCHK_SEND_BINARY:
3711 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3712 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3713 goto error;
3714 }
3715 break;
3716 case TCPCHK_SEND_STRING_LF:
3717 case TCPCHK_SEND_BINARY_LF:
3718 LIST_INIT(&chk->send.fmt);
3719 px->conf.args.ctx = ARGC_SRV;
3720 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3721 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3722 goto error;
3723 }
3724 break;
3725 case TCPCHK_SEND_HTTP:
3726 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003727 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003728 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729
Christopher Faulet61cc8522020-04-20 14:54:42 +02003730 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003731
Christopher Faulet61cc8522020-04-20 14:54:42 +02003732 error:
3733 free(chk);
3734 free(comment);
3735 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003736}
3737
Christopher Faulet61cc8522020-04-20 14:54:42 +02003738/* Parses and creates a http-check send rule. NULL is returned on error */
3739static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3740 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003742 struct tcpcheck_rule *chk = NULL;
3743 struct tcpcheck_http_hdr *hdr = NULL;
3744 struct http_hdr hdrs[global.tune.max_http_hdr];
3745 char *meth = NULL, *uri = NULL, *vsn = NULL;
3746 char *body = NULL, *comment = NULL;
3747 unsigned int flags = 0;
3748 int i = 0;
3749
3750 cur_arg++;
3751 while (*(args[cur_arg])) {
3752 if (strcmp(args[cur_arg], "meth") == 0) {
3753 if (!*(args[cur_arg+1])) {
3754 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3755 goto error;
3756 }
3757 cur_arg++;
3758 meth = args[cur_arg];
3759 }
3760 else if (strcmp(args[cur_arg], "uri") == 0) {
3761 if (!*(args[cur_arg+1])) {
3762 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3763 goto error;
3764 }
3765 cur_arg++;
3766 uri = args[cur_arg];
3767 // TODO: log-format uri
3768 }
3769 else if (strcmp(args[cur_arg], "vsn") == 0) {
3770 if (!*(args[cur_arg+1])) {
3771 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3772 goto error;
3773 }
3774 cur_arg++;
3775 vsn = args[cur_arg];
3776 }
3777 else if (strcmp(args[cur_arg], "hdr") == 0) {
3778 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3779 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3780 goto error;
3781 }
3782 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3783 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3784 i++;
3785 cur_arg += 2;
3786 }
3787 else if (strcmp(args[cur_arg], "body") == 0) {
3788 if (!*(args[cur_arg+1])) {
3789 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3790 goto error;
3791 }
3792 cur_arg++;
3793 body = args[cur_arg];
3794 // TODO: log-format body
3795 }
3796 else if (strcmp(args[cur_arg], "comment") == 0) {
3797 if (!*(args[cur_arg+1])) {
3798 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3799 goto error;
3800 }
3801 cur_arg++;
3802 free(comment);
3803 comment = strdup(args[cur_arg]);
3804 if (!comment) {
3805 memprintf(errmsg, "out of memory");
3806 goto error;
3807 }
3808 }
3809 else {
3810 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
3811 args[cur_arg]);
3812 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003814 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815 }
3816
Christopher Faulet61cc8522020-04-20 14:54:42 +02003817 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003818
Christopher Faulet61cc8522020-04-20 14:54:42 +02003819 chk = calloc(1, sizeof(*chk));
3820 if (!chk) {
3821 memprintf(errmsg, "out of memory");
3822 goto error;
3823 }
3824 chk->action = TCPCHK_ACT_SEND;
3825 chk->comment = comment; comment = NULL;
3826 chk->send.type = TCPCHK_SEND_HTTP;
3827 chk->send.http.flags = flags;
3828 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003829
Christopher Faulet61cc8522020-04-20 14:54:42 +02003830 if (meth) {
3831 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3832 chk->send.http.meth.str.area = strdup(meth);
3833 chk->send.http.meth.str.data = strlen(meth);
3834 if (!chk->send.http.meth.str.area) {
3835 memprintf(errmsg, "out of memory");
3836 goto error;
3837 }
3838 }
3839 if (uri) {
3840 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3841 if (!isttest(chk->send.http.uri)) {
3842 memprintf(errmsg, "out of memory");
3843 goto error;
3844 }
3845 }
3846 if (vsn) {
3847 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3848 if (!isttest(chk->send.http.vsn)) {
3849 memprintf(errmsg, "out of memory");
3850 goto error;
3851 }
3852 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003853 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003854 hdr = calloc(1, sizeof(*hdr));
3855 if (!hdr) {
3856 memprintf(errmsg, "out of memory");
3857 goto error;
3858 }
3859 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003860 hdr->name = istdup(hdrs[i].n);
3861 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003862 memprintf(errmsg, "out of memory");
3863 goto error;
3864 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003865
Christopher Fauletb61caf42020-04-21 10:57:42 +02003866 ist0(hdrs[i].v);
3867 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 +02003868 goto error;
3869 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3870 hdr = NULL;
3871 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003872
Christopher Faulet61cc8522020-04-20 14:54:42 +02003873 if (body) {
3874 chk->send.http.body = ist2(strdup(body), strlen(body));
3875 if (!isttest(chk->send.http.body)) {
3876 memprintf(errmsg, "out of memory");
3877 goto error;
3878 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003879 }
3880
Christopher Faulet61cc8522020-04-20 14:54:42 +02003881 return chk;
3882
3883 error:
3884 free_tcpcheck_http_hdr(hdr);
3885 free_tcpcheck(chk, 0);
3886 free(comment);
3887 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003888}
3889
Christopher Faulet61cc8522020-04-20 14:54:42 +02003890/* Parses and creates a http-check comment rule. NULL is returned on error */
3891static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3892 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003893{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003894 struct tcpcheck_rule *chk = NULL;
3895 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003896
Christopher Faulet61cc8522020-04-20 14:54:42 +02003897 if (!*(args[cur_arg+1])) {
3898 memprintf(errmsg, "expects a string as argument");
3899 goto error;
3900 }
3901 cur_arg++;
3902 comment = strdup(args[cur_arg]);
3903 if (!comment) {
3904 memprintf(errmsg, "out of memory");
3905 goto error;
3906 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003907
Christopher Faulet61cc8522020-04-20 14:54:42 +02003908 chk = calloc(1, sizeof(*chk));
3909 if (!chk) {
3910 memprintf(errmsg, "out of memory");
3911 goto error;
3912 }
3913 chk->action = TCPCHK_ACT_COMMENT;
3914 chk->comment = comment;
3915 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003916
Christopher Faulet61cc8522020-04-20 14:54:42 +02003917 error:
3918 free(comment);
3919 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003920}
3921
Christopher Faulet61cc8522020-04-20 14:54:42 +02003922/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3923 * on error. <proto> is set to the right protocol flags (covered by the
3924 * TCPCHK_RULES_PROTO_CHK mask).
3925 */
3926static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3927 struct list *rules, unsigned int proto,
3928 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003929{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003930 struct tcpcheck_rule *prev_check, *chk = NULL;
3931 struct sample_expr *status_expr = NULL;
3932 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
3933 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3934 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3935 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3936 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3937 long min_recv = -1;
3938 int inverse = 0, with_capture = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003939
Christopher Faulet61cc8522020-04-20 14:54:42 +02003940 str = on_success_msg = on_error_msg = comment = pattern = NULL;
3941 if (!*(args[cur_arg+1])) {
3942 memprintf(errmsg, "expects at least a matching pattern as arguments");
3943 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003944 }
3945
Christopher Faulet61cc8522020-04-20 14:54:42 +02003946 cur_arg++;
3947 while (*(args[cur_arg])) {
3948 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003949
Christopher Faulet61cc8522020-04-20 14:54:42 +02003950 rescan:
3951 if (strcmp(args[cur_arg], "min-recv") == 0) {
3952 if (in_pattern) {
3953 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3954 goto error;
3955 }
3956 if (!*(args[cur_arg+1])) {
3957 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3958 goto error;
3959 }
3960 /* Use an signed integer here because of chksize */
3961 cur_arg++;
3962 min_recv = atol(args[cur_arg]);
3963 if (min_recv < -1 || min_recv > INT_MAX) {
3964 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3965 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003966 }
3967 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003968 else if (*(args[cur_arg]) == '!') {
3969 in_pattern = 1;
3970 while (*(args[cur_arg]) == '!') {
3971 inverse = !inverse;
3972 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003973 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003974 if (!*(args[cur_arg]))
3975 cur_arg++;
3976 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003977 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003978 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3979 if (type != TCPCHK_EXPECT_UNDEF) {
3980 memprintf(errmsg, "only on pattern expected");
3981 goto error;
3982 }
3983 if (proto != TCPCHK_RULES_HTTP_CHK)
3984 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3985 else
3986 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003987
Christopher Faulet61cc8522020-04-20 14:54:42 +02003988 if (!*(args[cur_arg+1])) {
3989 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3990 goto error;
3991 }
3992 cur_arg++;
3993 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003994 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003995 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3996 if (proto == TCPCHK_RULES_HTTP_CHK)
3997 goto bad_http_kw;
3998 if (type != TCPCHK_EXPECT_UNDEF) {
3999 memprintf(errmsg, "only on pattern expected");
4000 goto error;
4001 }
4002 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004003
Christopher Faulet61cc8522020-04-20 14:54:42 +02004004 if (!*(args[cur_arg+1])) {
4005 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4006 goto error;
4007 }
4008 cur_arg++;
4009 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004010 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004011 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4012 if (proto != TCPCHK_RULES_HTTP_CHK)
4013 goto bad_tcp_kw;
4014 if (type != TCPCHK_EXPECT_UNDEF) {
4015 memprintf(errmsg, "only on pattern expected");
4016 goto error;
4017 }
4018 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004019
Christopher Faulet61cc8522020-04-20 14:54:42 +02004020 if (!*(args[cur_arg+1])) {
4021 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4022 goto error;
4023 }
4024 cur_arg++;
4025 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004026 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004027 else if (strcmp(args[cur_arg], "custom") == 0) {
4028 if (in_pattern) {
4029 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4030 goto error;
4031 }
4032 if (type != TCPCHK_EXPECT_UNDEF) {
4033 memprintf(errmsg, "only on pattern expected");
4034 goto error;
4035 }
4036 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004038 else if (strcmp(args[cur_arg], "comment") == 0) {
4039 if (in_pattern) {
4040 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4041 goto error;
4042 }
4043 if (!*(args[cur_arg+1])) {
4044 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4045 goto error;
4046 }
4047 cur_arg++;
4048 free(comment);
4049 comment = strdup(args[cur_arg]);
4050 if (!comment) {
4051 memprintf(errmsg, "out of memory");
4052 goto error;
4053 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004054 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004055 else if (strcmp(args[cur_arg], "on-success") == 0) {
4056 if (in_pattern) {
4057 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4058 goto error;
4059 }
4060 if (!*(args[cur_arg+1])) {
4061 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4062 goto error;
4063 }
4064 cur_arg++;
4065 free(on_success_msg);
4066 on_success_msg = strdup(args[cur_arg]);
4067 if (!on_success_msg) {
4068 memprintf(errmsg, "out of memory");
4069 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004070 }
4071 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004072 else if (strcmp(args[cur_arg], "on-error") == 0) {
4073 if (in_pattern) {
4074 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4075 goto error;
4076 }
4077 if (!*(args[cur_arg+1])) {
4078 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4079 goto error;
4080 }
4081 cur_arg++;
4082 free(on_error_msg);
4083 on_error_msg = strdup(args[cur_arg]);
4084 if (!on_error_msg) {
4085 memprintf(errmsg, "out of memory");
4086 goto error;
4087 }
4088 }
4089 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4090 if (in_pattern) {
4091 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4092 goto error;
4093 }
4094 if (!*(args[cur_arg+1])) {
4095 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4096 goto error;
4097 }
4098 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4099 ok_st = HCHK_STATUS_L7OKD;
4100 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4101 ok_st = HCHK_STATUS_L7OKCD;
4102 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4103 ok_st = HCHK_STATUS_L6OK;
4104 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4105 ok_st = HCHK_STATUS_L4OK;
4106 else {
4107 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4108 args[cur_arg], args[cur_arg+1]);
4109 goto error;
4110 }
4111 cur_arg++;
4112 }
4113 else if (strcmp(args[cur_arg], "error-status") == 0) {
4114 if (in_pattern) {
4115 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4116 goto error;
4117 }
4118 if (!*(args[cur_arg+1])) {
4119 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4120 goto error;
4121 }
4122 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4123 err_st = HCHK_STATUS_L7RSP;
4124 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4125 err_st = HCHK_STATUS_L7STS;
4126 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4127 err_st = HCHK_STATUS_L6RSP;
4128 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4129 err_st = HCHK_STATUS_L4CON;
4130 else {
4131 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4132 args[cur_arg], args[cur_arg+1]);
4133 goto error;
4134 }
4135 cur_arg++;
4136 }
4137 else if (strcmp(args[cur_arg], "status-code") == 0) {
4138 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004139
Christopher Faulet61cc8522020-04-20 14:54:42 +02004140 if (in_pattern) {
4141 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4142 goto error;
4143 }
4144 if (!*(args[cur_arg+1])) {
4145 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4146 goto error;
4147 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004148
Christopher Faulet61cc8522020-04-20 14:54:42 +02004149 cur_arg++;
4150 release_sample_expr(status_expr);
4151 px->conf.args.ctx = ARGC_SRV;
4152 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4153 file, line, errmsg, &px->conf.args, NULL);
4154 if (!status_expr) {
4155 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4156 goto error;
4157 }
4158 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4159 memprintf(errmsg, "error detected while parsing status-code expression : "
4160 " fetch method '%s' extracts information from '%s', "
4161 "none of which is available here.\n",
4162 args[cur_arg], sample_src_names(status_expr->fetch->use));
4163 goto error;
4164 }
4165 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4166 }
4167 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4168 if (in_pattern) {
4169 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4170 goto error;
4171 }
4172 if (!*(args[cur_arg+1])) {
4173 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4174 goto error;
4175 }
4176 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4177 tout_st = HCHK_STATUS_L7TOUT;
4178 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4179 tout_st = HCHK_STATUS_L6TOUT;
4180 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4181 tout_st = HCHK_STATUS_L4TOUT;
4182 else {
4183 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4184 args[cur_arg], args[cur_arg+1]);
4185 goto error;
4186 }
4187 cur_arg++;
4188 }
4189 else {
4190 if (proto == TCPCHK_RULES_HTTP_CHK) {
4191 bad_http_kw:
4192 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4193 " or comment but got '%s' as argument.", args[cur_arg]);
4194 }
4195 else {
4196 bad_tcp_kw:
4197 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4198 " or comment but got '%s' as argument.", args[cur_arg]);
4199 }
4200 goto error;
4201 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004202
Christopher Faulet61cc8522020-04-20 14:54:42 +02004203 cur_arg++;
4204 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004205
Christopher Faulet61cc8522020-04-20 14:54:42 +02004206 if (comment) {
4207 char *p = comment;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004208
Christopher Faulet61cc8522020-04-20 14:54:42 +02004209 while (*p) {
4210 if (*p == '\\') {
4211 p++;
4212 if (!*p || !isdigit((unsigned char)*p) ||
4213 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4214 memprintf(errmsg, "invalid backreference in 'comment' argument");
4215 goto error;
4216 }
4217 with_capture = 1;
4218 }
4219 p++;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004220 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004221 if (with_capture && !inverse)
4222 memprintf(errmsg, "using backreference in a positive expect comment is useless");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004223 }
4224
Christopher Faulet61cc8522020-04-20 14:54:42 +02004225 chk = calloc(1, sizeof(*chk));
4226 if (!chk) {
4227 memprintf(errmsg, "out of memory");
4228 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004229 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004230 chk->action = TCPCHK_ACT_EXPECT;
4231 LIST_INIT(&chk->expect.onerror_fmt);
4232 LIST_INIT(&chk->expect.onsuccess_fmt);
4233 chk->comment = comment; comment = NULL;
4234 chk->expect.type = type;
4235 chk->expect.min_recv = min_recv;
4236 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
4237 chk->expect.flags |= (with_capture ? TCPCHK_EXPT_FL_CAP : 0);
4238 chk->expect.ok_status = ok_st;
4239 chk->expect.err_status = err_st;
4240 chk->expect.tout_status = tout_st;
4241 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004242
Christopher Faulet61cc8522020-04-20 14:54:42 +02004243 if (on_success_msg) {
4244 px->conf.args.ctx = ARGC_SRV;
4245 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4246 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4247 goto error;
4248 }
4249 free(on_success_msg);
4250 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004251 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004252 if (on_error_msg) {
4253 px->conf.args.ctx = ARGC_SRV;
4254 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4255 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4256 goto error;
4257 }
4258 free(on_error_msg);
4259 on_error_msg = NULL;
4260 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004261
Christopher Faulet61cc8522020-04-20 14:54:42 +02004262 switch (chk->expect.type) {
4263 case TCPCHK_EXPECT_STRING:
4264 case TCPCHK_EXPECT_HTTP_STATUS:
4265 case TCPCHK_EXPECT_HTTP_BODY:
4266 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004267 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004268 memprintf(errmsg, "out of memory");
4269 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004270 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004271 break;
4272 case TCPCHK_EXPECT_BINARY:
4273 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4274 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4275 goto error;
4276 }
4277 case TCPCHK_EXPECT_REGEX:
4278 case TCPCHK_EXPECT_REGEX_BINARY:
4279 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4280 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
4281 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
4282 if (!chk->expect.regex)
4283 goto error;
4284 break;
4285 case TCPCHK_EXPECT_CUSTOM:
4286 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4287 break;
4288 case TCPCHK_EXPECT_UNDEF:
4289 free(chk);
4290 memprintf(errmsg, "pattern not found");
4291 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004292 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004293
Christopher Faulet61cc8522020-04-20 14:54:42 +02004294 /* All tcp-check expect points back to the first inverse expect rule in
4295 * a chain of one or more expect rule, potentially itself.
4296 */
4297 chk->expect.head = chk;
4298 list_for_each_entry_rev(prev_check, rules, list) {
4299 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4300 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4301 chk->expect.head = prev_check;
4302 continue;
4303 }
4304 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4305 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004306 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004307 return chk;
4308
4309 error:
4310 free_tcpcheck(chk, 0);
4311 free(str);
4312 free(comment);
4313 free(on_success_msg);
4314 free(on_error_msg);
4315 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004316 return NULL;
4317}
4318
Christopher Faulet61cc8522020-04-20 14:54:42 +02004319/* Overwrites fields of the old http send rule with those of the new one. When
4320 * replaced, old values are freed and replaced by the new ones. New values are
4321 * not copied but transferred. At the end <new> should be empty and can be
4322 * safely released. This function never fails.
4323 */
4324static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004325{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004326 struct logformat_node *lf, *lfb;
4327 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004328
Christopher Faulet404f9192020-04-09 23:13:54 +02004329
Christopher Faulet61cc8522020-04-20 14:54:42 +02004330 if (new->send.http.meth.str.area) {
4331 free(old->send.http.meth.str.area);
4332 old->send.http.meth.meth = new->send.http.meth.meth;
4333 old->send.http.meth.str.area = new->send.http.meth.str.area;
4334 old->send.http.meth.str.data = new->send.http.meth.str.data;
4335 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004336 }
4337
Christopher Faulet61cc8522020-04-20 14:54:42 +02004338 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4339 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004340 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004341 else
4342 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4343 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4344 old->send.http.uri = new->send.http.uri;
4345 new->send.http.uri = IST_NULL;
4346 }
4347 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4348 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004349 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004350 else
4351 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4352 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4353 LIST_INIT(&old->send.http.uri_fmt);
4354 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4355 LIST_DEL(&lf->list);
4356 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4357 }
4358 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004359
Christopher Faulet61cc8522020-04-20 14:54:42 +02004360 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004361 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004362 old->send.http.vsn = new->send.http.vsn;
4363 new->send.http.vsn = IST_NULL;
4364 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004365
Christopher Faulet61cc8522020-04-20 14:54:42 +02004366 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4367 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4368 LIST_DEL(&hdr->list);
4369 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004370 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004371
4372 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4373 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004374 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004375 else
4376 free_tcpcheck_fmt(&old->send.http.body_fmt);
4377 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4378 old->send.http.body = new->send.http.body;
4379 new->send.http.body = IST_NULL;
4380 }
4381 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4382 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004383 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004384 else
4385 free_tcpcheck_fmt(&old->send.http.body_fmt);
4386 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4387 LIST_INIT(&old->send.http.body_fmt);
4388 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4389 LIST_DEL(&lf->list);
4390 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4391 }
4392 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004393}
4394
Christopher Faulet61cc8522020-04-20 14:54:42 +02004395/* Internal function used to add an http-check rule in a list during the config
4396 * parsing step. Depending on its type, and the previously inserted rules, a
4397 * specific action may be performed or an error may be reported. This functions
4398 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4399 * message.
4400 */
4401static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004402{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004403 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004404
Christopher Faulet61cc8522020-04-20 14:54:42 +02004405 /* the implicit send rule coming from an "option httpchk" line must be
4406 * merged with the first explici http-check send rule, if
4407 * any. Depdending the declaration order some tests are required.
4408 *
4409 * Some tests is also required for other kinds of http-check rules to be
4410 * sure the ruleset remains valid.
4411 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004412
Christopher Faulet61cc8522020-04-20 14:54:42 +02004413 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4414 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4415 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4416 * following tests are performed :
4417 *
4418 * 1- If there is no such rule or if it is not a send rule, the implicit send
4419 * rule is pushed in front of the ruleset
4420 *
4421 * 2- If it is another implicit send rule, it is replaced with the new one.
4422 *
4423 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4424 * both, overwritting the old send rule (the explicit one) with info of the
4425 * new send rule (the implicit one).
4426 */
4427 r = get_first_tcpcheck_rule(rules);
4428 if (r && r->action == TCPCHK_ACT_CONNECT)
4429 r = get_next_tcpcheck_rule(rules, r);
4430 if (!r || r->action != TCPCHK_ACT_SEND)
4431 LIST_ADD(rules->list, &chk->list);
4432 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4433 LIST_DEL(&r->list);
4434 free_tcpcheck(r, 0);
4435 LIST_ADD(rules->list, &chk->list);
4436 }
4437 else {
4438 tcpcheck_overwrite_send_http_rule(r, chk);
4439 free_tcpcheck(chk, 0);
4440 }
4441 }
4442 else {
4443 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4444 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4445 * with an existing implicit send rule, if any. At the end, if there is no error,
4446 * the rule is appended to the list.
4447 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004448
Christopher Faulet61cc8522020-04-20 14:54:42 +02004449 r = get_last_tcpcheck_rule(rules);
4450 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4451 /* no error */;
4452 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4453 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4454 chk->index+1);
4455 return 0;
4456 }
4457 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4458 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4459 chk->index+1);
4460 return 0;
4461 }
4462 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4463 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4464 chk->index+1);
4465 return 0;
4466 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004467
Christopher Faulet61cc8522020-04-20 14:54:42 +02004468 if (chk->action == TCPCHK_ACT_SEND) {
4469 r = get_first_tcpcheck_rule(rules);
4470 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4471 tcpcheck_overwrite_send_http_rule(r, chk);
4472 free_tcpcheck(chk, 0);
4473 LIST_DEL(&r->list);
4474 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4475 chk = r;
4476 }
4477 }
4478 LIST_ADDQ(rules->list, &chk->list);
4479 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004480 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004481}
4482
Christopher Faulet61cc8522020-04-20 14:54:42 +02004483/**************************************************************************/
4484/************************** Init/deinit checks ****************************/
4485/**************************************************************************/
4486static const char *init_check(struct check *check, int type)
4487{
4488 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004489
Christopher Faulet61cc8522020-04-20 14:54:42 +02004490 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4491 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004492
Christopher Faulet61cc8522020-04-20 14:54:42 +02004493 check->bi.area = calloc(check->bi.size, sizeof(char));
4494 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004495
Christopher Faulet61cc8522020-04-20 14:54:42 +02004496 if (!check->bi.area || !check->bo.area)
4497 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004498
Christopher Faulet61cc8522020-04-20 14:54:42 +02004499 check->wait_list.tasklet = tasklet_new();
4500 if (!check->wait_list.tasklet)
4501 return "out of memory while allocating check tasklet";
4502 check->wait_list.events = 0;
4503 check->wait_list.tasklet->process = event_srv_chk_io;
4504 check->wait_list.tasklet->context = check;
4505 return NULL;
4506}
4507
4508void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004509{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004510 task_destroy(check->task);
4511 if (check->wait_list.tasklet)
4512 tasklet_free(check->wait_list.tasklet);
4513
4514 free(check->bi.area);
4515 free(check->bo.area);
4516 if (check->cs) {
4517 free(check->cs->conn);
4518 check->cs->conn = NULL;
4519 cs_free(check->cs);
4520 check->cs = NULL;
4521 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004522}
4523
Christopher Faulet61cc8522020-04-20 14:54:42 +02004524/* manages a server health-check. Returns the time the task accepts to wait, or
4525 * TIME_ETERNITY for infinity.
4526 */
4527static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004528{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004529 struct check *check = context;
4530
4531 if (check->type == PR_O2_EXT_CHK)
4532 return process_chk_proc(t, context, state);
4533 return process_chk_conn(t, context, state);
4534
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004535}
4536
Christopher Faulet61cc8522020-04-20 14:54:42 +02004537
4538static int start_check_task(struct check *check, int mininter,
4539 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004540{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004541 struct task *t;
4542 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004543
Christopher Faulet61cc8522020-04-20 14:54:42 +02004544 if (check->type == PR_O2_EXT_CHK)
4545 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004546
Christopher Faulet61cc8522020-04-20 14:54:42 +02004547 /* task for the check */
4548 if ((t = task_new(thread_mask)) == NULL) {
4549 ha_alert("Starting [%s:%s] check: out of memory.\n",
4550 check->server->proxy->id, check->server->id);
4551 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004552 }
4553
Christopher Faulet61cc8522020-04-20 14:54:42 +02004554 check->task = t;
4555 t->process = process_chk;
4556 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004557
Christopher Faulet61cc8522020-04-20 14:54:42 +02004558 if (mininter < srv_getinter(check))
4559 mininter = srv_getinter(check);
4560
4561 if (global.max_spread_checks && mininter > global.max_spread_checks)
4562 mininter = global.max_spread_checks;
4563
4564 /* check this every ms */
4565 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4566 check->start = now;
4567 task_queue(t);
4568
4569 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004570}
4571
Christopher Faulet61cc8522020-04-20 14:54:42 +02004572/* updates the server's weight during a warmup stage. Once the final weight is
4573 * reached, the task automatically stops. Note that any server status change
4574 * must have updated s->last_change accordingly.
4575 */
4576static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004577{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004578 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004579
Christopher Faulet61cc8522020-04-20 14:54:42 +02004580 /* by default, plan on stopping the task */
4581 t->expire = TICK_ETERNITY;
4582 if ((s->next_admin & SRV_ADMF_MAINT) ||
4583 (s->next_state != SRV_ST_STARTING))
4584 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004585
Christopher Faulet61cc8522020-04-20 14:54:42 +02004586 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004587
Christopher Faulet61cc8522020-04-20 14:54:42 +02004588 /* recalculate the weights and update the state */
4589 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004590
Christopher Faulet61cc8522020-04-20 14:54:42 +02004591 /* probably that we can refill this server with a bit more connections */
4592 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004593
Christopher Faulet61cc8522020-04-20 14:54:42 +02004594 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004595
Christopher Faulet61cc8522020-04-20 14:54:42 +02004596 /* get back there in 1 second or 1/20th of the slowstart interval,
4597 * whichever is greater, resulting in small 5% steps.
4598 */
4599 if (s->next_state == SRV_ST_STARTING)
4600 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4601 return t;
4602}
4603
4604/*
4605 * Start health-check.
4606 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4607 */
4608static int start_checks()
4609{
4610
4611 struct proxy *px;
4612 struct server *s;
4613 struct task *t;
4614 int nbcheck=0, mininter=0, srvpos=0;
4615
4616 /* 0- init the dummy frontend used to create all checks sessions */
4617 init_new_proxy(&checks_fe);
4618 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4619 checks_fe.mode = PR_MODE_TCP;
4620 checks_fe.maxconn = 0;
4621 checks_fe.conn_retries = CONN_RETRIES;
4622 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4623 checks_fe.timeout.client = TICK_ETERNITY;
4624
4625 /* 1- count the checkers to run simultaneously.
4626 * We also determine the minimum interval among all of those which
4627 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4628 * will be used to spread their start-up date. Those which have
4629 * a shorter interval will start independently and will not dictate
4630 * too short an interval for all others.
4631 */
4632 for (px = proxies_list; px; px = px->next) {
4633 for (s = px->srv; s; s = s->next) {
4634 if (s->slowstart) {
4635 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4636 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4637 return ERR_ALERT | ERR_FATAL;
4638 }
4639 /* We need a warmup task that will be called when the server
4640 * state switches from down to up.
4641 */
4642 s->warmup = t;
4643 t->process = server_warmup;
4644 t->context = s;
4645 /* server can be in this state only because of */
4646 if (s->next_state == SRV_ST_STARTING)
4647 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 +02004648 }
4649
Christopher Faulet61cc8522020-04-20 14:54:42 +02004650 if (s->check.state & CHK_ST_CONFIGURED) {
4651 nbcheck++;
4652 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4653 (!mininter || mininter > srv_getinter(&s->check)))
4654 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004655 }
4656
Christopher Faulet61cc8522020-04-20 14:54:42 +02004657 if (s->agent.state & CHK_ST_CONFIGURED) {
4658 nbcheck++;
4659 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4660 (!mininter || mininter > srv_getinter(&s->agent)))
4661 mininter = srv_getinter(&s->agent);
4662 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004663 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004664 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004665
Christopher Faulet61cc8522020-04-20 14:54:42 +02004666 if (!nbcheck)
4667 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004668
Christopher Faulet61cc8522020-04-20 14:54:42 +02004669 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004670
Christopher Faulet61cc8522020-04-20 14:54:42 +02004671 /*
4672 * 2- start them as far as possible from each others. For this, we will
4673 * start them after their interval set to the min interval divided by
4674 * the number of servers, weighted by the server's position in the list.
4675 */
4676 for (px = proxies_list; px; px = px->next) {
4677 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4678 if (init_pid_list()) {
4679 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4680 return ERR_ALERT | ERR_FATAL;
4681 }
4682 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004683
Christopher Faulet61cc8522020-04-20 14:54:42 +02004684 for (s = px->srv; s; s = s->next) {
4685 /* A task for the main check */
4686 if (s->check.state & CHK_ST_CONFIGURED) {
4687 if (s->check.type == PR_O2_EXT_CHK) {
4688 if (!prepare_external_check(&s->check))
4689 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004690 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004691 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4692 return ERR_ALERT | ERR_FATAL;
4693 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004694 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004695
Christopher Faulet61cc8522020-04-20 14:54:42 +02004696 /* A task for a auxiliary agent check */
4697 if (s->agent.state & CHK_ST_CONFIGURED) {
4698 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4699 return ERR_ALERT | ERR_FATAL;
4700 }
4701 srvpos++;
4702 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004703 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004704 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004705 return 0;
4706}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004707
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004708
Christopher Faulet61cc8522020-04-20 14:54:42 +02004709/*
4710 * Return value:
4711 * the port to be used for the health check
4712 * 0 in case no port could be found for the check
4713 */
4714static int srv_check_healthcheck_port(struct check *chk)
4715{
4716 int i = 0;
4717 struct server *srv = NULL;
4718
4719 srv = chk->server;
4720
4721 /* by default, we use the health check port ocnfigured */
4722 if (chk->port > 0)
4723 return chk->port;
4724
4725 /* try to get the port from check_core.addr if check.port not set */
4726 i = get_host_port(&chk->addr);
4727 if (i > 0)
4728 return i;
4729
4730 /* try to get the port from server address */
4731 /* prevent MAPPORTS from working at this point, since checks could
4732 * not be performed in such case (MAPPORTS impose a relative ports
4733 * based on live traffic)
4734 */
4735 if (srv->flags & SRV_F_MAPPORTS)
4736 return 0;
4737
4738 i = srv->svc_port; /* by default */
4739 if (i > 0)
4740 return i;
4741
4742 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004743}
4744
Christopher Faulet61cc8522020-04-20 14:54:42 +02004745/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4746 * if an error occurred.
4747 */
4748static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004749{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004750 const char *err;
4751 struct tcpcheck_rule *r;
4752 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004753
Christopher Faulet61cc8522020-04-20 14:54:42 +02004754 if (!srv->do_check)
4755 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004756
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004757
Christopher Faulet61cc8522020-04-20 14:54:42 +02004758 /* If neither a port nor an addr was specified and no check transport
4759 * layer is forced, then the transport layer used by the checks is the
4760 * same as for the production traffic. Otherwise we use raw_sock by
4761 * default, unless one is specified.
4762 */
4763 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4764 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4765 srv->check.use_ssl = srv->use_ssl;
4766 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004767 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004768 else if (srv->check.use_ssl == 1)
4769 srv->check.xprt = xprt_get(XPRT_SSL);
4770 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004771 }
4772
Christopher Faulet61cc8522020-04-20 14:54:42 +02004773 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004774
Christopher Faulet61cc8522020-04-20 14:54:42 +02004775 /* We need at least a service port, a check port or the first tcp-check
4776 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4777 */
4778 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4779 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4780 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004781
Christopher Faulet61cc8522020-04-20 14:54:42 +02004782 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4783 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4784 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4785 ret |= ERR_ALERT | ERR_ABORT;
4786 goto out;
4787 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004788
Christopher Faulet61cc8522020-04-20 14:54:42 +02004789 /* search the first action (connect / send / expect) in the list */
4790 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4791 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4792 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4793 "nor tcp_check rule 'connect' with port information.\n",
4794 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4795 ret |= ERR_ALERT | ERR_ABORT;
4796 goto out;
4797 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004798
Christopher Faulet61cc8522020-04-20 14:54:42 +02004799 /* scan the tcp-check ruleset to ensure a port has been configured */
4800 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4801 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4802 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4803 "and a tcp_check rule 'connect' with no port information.\n",
4804 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4805 ret |= ERR_ALERT | ERR_ABORT;
4806 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004807 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004808 }
4809
Christopher Faulet61cc8522020-04-20 14:54:42 +02004810 init:
4811 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4812 struct tcpcheck_ruleset *rs = NULL;
4813 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4814 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004815
Christopher Faulet61cc8522020-04-20 14:54:42 +02004816 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4817 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004818
Christopher Faulet61cc8522020-04-20 14:54:42 +02004819 rs = find_tcpcheck_ruleset("*tcp-check");
4820 if (!rs) {
4821 rs = create_tcpcheck_ruleset("*tcp-check");
4822 if (rs == NULL) {
4823 ha_alert("config: %s '%s': out of memory.\n",
4824 proxy_type_str(srv->proxy), srv->proxy->id);
4825 ret |= ERR_ALERT | ERR_FATAL;
4826 goto out;
4827 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004828 }
4829
Christopher Faulet61cc8522020-04-20 14:54:42 +02004830 free_tcpcheck_vars(&rules->preset_vars);
4831 rules->list = &rs->rules;
4832 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004833 }
4834
Christopher Faulet61cc8522020-04-20 14:54:42 +02004835 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4836 if (err) {
4837 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4838 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4839 ret |= ERR_ALERT | ERR_ABORT;
4840 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004841 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004842 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4843 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004844
Christopher Faulet61cc8522020-04-20 14:54:42 +02004845 out:
4846 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004847}
4848
Christopher Faulet61cc8522020-04-20 14:54:42 +02004849/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4850 * if an error occurred.
4851 */
4852static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004853{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004854 struct tcpcheck_rule *chk;
4855 const char *err;
4856 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004857
Christopher Faulet61cc8522020-04-20 14:54:42 +02004858 if (!srv->do_agent)
4859 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004860
Christopher Faulet61cc8522020-04-20 14:54:42 +02004861 /* If there is no connect rule preceeding all send / expect rules, an
4862 * implicit one is inserted before all others.
4863 */
4864 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4865 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4866 chk = calloc(1, sizeof(*chk));
4867 if (!chk) {
4868 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4869 " to agent-check for server '%s' (out of memory).\n",
4870 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4871 ret |= ERR_ALERT | ERR_FATAL;
4872 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004873 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004874 chk->action = TCPCHK_ACT_CONNECT;
4875 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4876 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004877 }
4878
Christopher Faulete5870d82020-04-15 11:32:03 +02004879
Christopher Faulet61cc8522020-04-20 14:54:42 +02004880 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4881 if (err) {
4882 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4883 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4884 ret |= ERR_ALERT | ERR_ABORT;
4885 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004886 }
4887
Christopher Faulet61cc8522020-04-20 14:54:42 +02004888 if (!srv->agent.inter)
4889 srv->agent.inter = srv->check.inter;
4890
4891 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4892 global.maxsock++;
4893
4894 out:
4895 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004896}
4897
Christopher Faulet61cc8522020-04-20 14:54:42 +02004898/* Check tcp-check health-check configuration for the proxy <px>. */
4899static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004900{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004901 struct tcpcheck_rule *chk, *back;
4902 char *comment = NULL, *errmsg = NULL;
4903 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4904 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004905
Christopher Faulet61cc8522020-04-20 14:54:42 +02004906 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4907 deinit_proxy_tcpcheck(px);
4908 goto out;
4909 }
4910
4911 free(px->check_command);
4912 free(px->check_path);
4913 px->check_command = px->check_path = NULL;
4914
4915 if (!px->tcpcheck_rules.list) {
4916 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4917 ret |= ERR_ALERT | ERR_FATAL;
4918 goto out;
4919 }
4920
4921 /* HTTP ruleset only : */
4922 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4923 struct tcpcheck_rule *next;
4924
4925 /* move remaining implicit send rule from "option httpchk" line to the right place.
4926 * If such rule exists, it must be the first one. In this case, the rule is moved
4927 * after the first connect rule, if any. Otherwise, nothing is done.
4928 */
4929 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4930 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4931 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4932 if (next && next->action == TCPCHK_ACT_CONNECT) {
4933 LIST_DEL(&chk->list);
4934 LIST_ADD(&next->list, &chk->list);
4935 chk->index = next->index;
4936 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004937 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004938
4939 /* add implicit expect rule if the last one is a send. It is inherited from previous
4940 * versions where the http expect rule was optional. Now it is possible to chained
4941 * send/expect rules but the last expect may still be implicit.
4942 */
4943 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4944 if (chk && chk->action == TCPCHK_ACT_SEND) {
4945 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
4946 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4947 px->conf.file, px->conf.line, &errmsg);
4948 if (!next) {
4949 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4950 "(%s).\n", px->id, errmsg);
4951 free(errmsg);
4952 ret |= ERR_ALERT | ERR_FATAL;
4953 goto out;
4954 }
4955 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4956 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004957 }
4958 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004959
4960 /* For all ruleset: */
4961
4962 /* If there is no connect rule preceeding all send / expect rules, an
4963 * implicit one is inserted before all others.
4964 */
4965 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4966 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4967 chk = calloc(1, sizeof(*chk));
4968 if (!chk) {
4969 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4970 "(out of memory).\n", px->id);
4971 ret |= ERR_ALERT | ERR_FATAL;
4972 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004973 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004974 chk->action = TCPCHK_ACT_CONNECT;
4975 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4976 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4977 }
4978
4979 /* Remove all comment rules. To do so, when a such rule is found, the
4980 * comment is assigned to the following rule(s).
4981 */
4982 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4983 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4984 free(comment);
4985 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004986 }
4987
Christopher Faulet61cc8522020-04-20 14:54:42 +02004988 prev_action = chk->action;
4989 switch (chk->action) {
4990 case TCPCHK_ACT_COMMENT:
4991 free(comment);
4992 comment = chk->comment;
4993 LIST_DEL(&chk->list);
4994 free(chk);
4995 break;
4996 case TCPCHK_ACT_CONNECT:
4997 if (!chk->comment && comment)
4998 chk->comment = strdup(comment);
4999 /* fall though */
5000 case TCPCHK_ACT_ACTION_KW:
5001 free(comment);
5002 comment = NULL;
5003 break;
5004 case TCPCHK_ACT_SEND:
5005 case TCPCHK_ACT_EXPECT:
5006 if (!chk->comment && comment)
5007 chk->comment = strdup(comment);
5008 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005009 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005010 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005011 free(comment);
5012 comment = NULL;
5013
5014 out:
5015 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005016}
5017
Christopher Faulet61cc8522020-04-20 14:54:42 +02005018void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005019{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005020 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5021 px->tcpcheck_rules.flags = 0;
5022 px->tcpcheck_rules.list = NULL;
5023}
Christopher Faulete5870d82020-04-15 11:32:03 +02005024
Christopher Faulet61cc8522020-04-20 14:54:42 +02005025static void deinit_srv_check(struct server *srv)
5026{
5027 if (srv->check.state & CHK_ST_CONFIGURED)
5028 free_check(&srv->check);
5029 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5030 srv->do_check = 0;
5031}
Christopher Faulete5870d82020-04-15 11:32:03 +02005032
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033
5034static void deinit_srv_agent_check(struct server *srv)
5035{
5036 if (srv->agent.tcpcheck_rules) {
5037 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5038 free(srv->agent.tcpcheck_rules);
5039 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005040 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005041
Christopher Faulet61cc8522020-04-20 14:54:42 +02005042 if (srv->agent.state & CHK_ST_CONFIGURED)
5043 free_check(&srv->agent);
5044
5045 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5046 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005047}
5048
Christopher Faulet61cc8522020-04-20 14:54:42 +02005049static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005050{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005051 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005052 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005053 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005054
Christopher Fauletd7cee712020-04-21 13:45:00 +02005055 node = ebpt_first(&shared_tcpchecks);
5056 while (node) {
5057 next = ebpt_next(node);
5058 ebpt_delete(node);
5059 free(node->key);
5060 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005061 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5062 LIST_DEL(&r->list);
5063 free_tcpcheck(r, 0);
5064 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005065 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005066 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005067 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005068}
Christopher Faulete5870d82020-04-15 11:32:03 +02005069
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005070
Christopher Faulet61cc8522020-04-20 14:54:42 +02005071REGISTER_POST_SERVER_CHECK(init_srv_check);
5072REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5073REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5074REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005075
Christopher Faulet61cc8522020-04-20 14:54:42 +02005076REGISTER_SERVER_DEINIT(deinit_srv_check);
5077REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5078REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5079REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005080
Christopher Faulet61cc8522020-04-20 14:54:42 +02005081/**************************************************************************/
5082/****************************** Email alerts ******************************/
5083/* NOTE: It may be pertinent to use an applet to handle email alerts */
5084/* instead of a tcp-check ruleset */
5085/**************************************************************************/
5086void email_alert_free(struct email_alert *alert)
5087{
5088 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005089
Christopher Faulet61cc8522020-04-20 14:54:42 +02005090 if (!alert)
5091 return;
5092
5093 if (alert->rules.list) {
5094 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5095 LIST_DEL(&rule->list);
5096 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005097 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005098 free_tcpcheck_vars(&alert->rules.preset_vars);
5099 free(alert->rules.list);
5100 alert->rules.list = NULL;
5101 }
5102 pool_free(pool_head_email_alert, alert);
5103}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005104
Christopher Faulet61cc8522020-04-20 14:54:42 +02005105static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5106{
5107 struct check *check = context;
5108 struct email_alertq *q;
5109 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005110
Christopher Faulet61cc8522020-04-20 14:54:42 +02005111 q = container_of(check, typeof(*q), check);
5112
5113 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5114 while (1) {
5115 if (!(check->state & CHK_ST_ENABLED)) {
5116 if (LIST_ISEMPTY(&q->email_alerts)) {
5117 /* All alerts processed, queue the task */
5118 t->expire = TICK_ETERNITY;
5119 task_queue(t);
5120 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005121 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005122
5123 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5124 LIST_DEL(&alert->list);
5125 t->expire = now_ms;
5126 check->tcpcheck_rules = &alert->rules;
5127 check->status = HCHK_STATUS_INI;
5128 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005129 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005130
5131 process_chk(t, context, state);
5132 if (check->state & CHK_ST_INPROGRESS)
5133 break;
5134
5135 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5136 email_alert_free(alert);
5137 check->tcpcheck_rules = NULL;
5138 check->server = NULL;
5139 check->state &= ~CHK_ST_ENABLED;
5140 }
5141 end:
5142 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5143 return t;
5144}
5145
5146/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5147 *
5148 * The function returns 1 in success case, otherwise, it returns 0 and err is
5149 * filled.
5150 */
5151int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5152{
5153 struct mailer *mailer;
5154 struct email_alertq *queues;
5155 const char *err_str;
5156 int i = 0;
5157
5158 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5159 memprintf(err, "out of memory while allocating mailer alerts queues");
5160 goto fail_no_queue;
5161 }
5162
5163 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5164 struct email_alertq *q = &queues[i];
5165 struct check *check = &q->check;
5166 struct task *t;
5167
5168 LIST_INIT(&q->email_alerts);
5169 HA_SPIN_INIT(&q->lock);
5170 check->inter = mls->timeout.mail;
5171 check->rise = DEF_AGENT_RISETIME;
5172 check->proxy = p;
5173 check->fall = DEF_AGENT_FALLTIME;
5174 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5175 memprintf(err, "%s", err_str);
5176 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005177 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005178
5179 check->xprt = mailer->xprt;
5180 check->addr = mailer->addr;
5181 check->port = get_host_port(&mailer->addr);
5182
5183 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5184 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005185 goto error;
5186 }
5187
Christopher Faulet61cc8522020-04-20 14:54:42 +02005188 check->task = t;
5189 t->process = process_email_alert;
5190 t->context = check;
5191
5192 /* check this in one ms */
5193 t->expire = TICK_ETERNITY;
5194 check->start = now;
5195 task_queue(t);
5196 }
5197
5198 mls->users++;
5199 free(p->email_alert.mailers.name);
5200 p->email_alert.mailers.m = mls;
5201 p->email_alert.queues = queues;
5202 return 0;
5203
5204 error:
5205 for (i = 0; i < mls->count; i++) {
5206 struct email_alertq *q = &queues[i];
5207 struct check *check = &q->check;
5208
5209 free_check(check);
5210 }
5211 free(queues);
5212 fail_no_queue:
5213 return 1;
5214}
5215
5216static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5217{
5218 struct tcpcheck_rule *tcpcheck, *prev_check;
5219 struct tcpcheck_expect *expect;
5220
5221 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5222 return 0;
5223 memset(tcpcheck, 0, sizeof(*tcpcheck));
5224 tcpcheck->action = TCPCHK_ACT_EXPECT;
5225
5226 expect = &tcpcheck->expect;
5227 expect->type = TCPCHK_EXPECT_STRING;
5228 LIST_INIT(&expect->onerror_fmt);
5229 LIST_INIT(&expect->onsuccess_fmt);
5230 expect->ok_status = HCHK_STATUS_L7OKD;
5231 expect->err_status = HCHK_STATUS_L7RSP;
5232 expect->tout_status = HCHK_STATUS_L7TOUT;
5233 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005234 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005235 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5236 return 0;
5237 }
5238
5239 /* All tcp-check expect points back to the first inverse expect rule
5240 * in a chain of one or more expect rule, potentially itself.
5241 */
5242 tcpcheck->expect.head = tcpcheck;
5243 list_for_each_entry_rev(prev_check, rules->list, list) {
5244 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5245 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5246 tcpcheck->expect.head = prev_check;
5247 continue;
5248 }
5249 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5250 break;
5251 }
5252 LIST_ADDQ(rules->list, &tcpcheck->list);
5253 return 1;
5254}
5255
5256static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5257{
5258 struct tcpcheck_rule *tcpcheck;
5259 struct tcpcheck_send *send;
5260 const char *in;
5261 char *dst;
5262 int i;
5263
5264 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5265 return 0;
5266 memset(tcpcheck, 0, sizeof(*tcpcheck));
5267 tcpcheck->action = TCPCHK_ACT_SEND;
5268
5269 send = &tcpcheck->send;
5270 send->type = TCPCHK_SEND_STRING;
5271
5272 for (i = 0; strs[i]; i++)
5273 send->data.len += strlen(strs[i]);
5274
Christopher Fauletb61caf42020-04-21 10:57:42 +02005275 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005276 if (!isttest(send->data)) {
5277 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5278 return 0;
5279 }
5280
Christopher Fauletb61caf42020-04-21 10:57:42 +02005281 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005282 for (i = 0; strs[i]; i++)
5283 for (in = strs[i]; (*dst = *in++); dst++);
5284 *dst = 0;
5285
5286 LIST_ADDQ(rules->list, &tcpcheck->list);
5287 return 1;
5288}
5289
5290static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5291 struct email_alertq *q, const char *msg)
5292{
5293 struct email_alert *alert;
5294 struct tcpcheck_rule *tcpcheck;
5295 struct check *check = &q->check;
5296
5297 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5298 goto error;
5299 LIST_INIT(&alert->list);
5300 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5301 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5302 if (!alert->rules.list)
5303 goto error;
5304 LIST_INIT(alert->rules.list);
5305 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5306 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005307
Christopher Faulet61cc8522020-04-20 14:54:42 +02005308 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5309 goto error;
5310 memset(tcpcheck, 0, sizeof(*tcpcheck));
5311 tcpcheck->action = TCPCHK_ACT_CONNECT;
5312 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005313
Christopher Faulet61cc8522020-04-20 14:54:42 +02005314 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005315
Christopher Faulet61cc8522020-04-20 14:54:42 +02005316 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005317 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005318
5319 {
5320 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5321 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5322 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005323 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005324
Christopher Faulet61cc8522020-04-20 14:54:42 +02005325 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5326 goto error;
5327
5328 {
5329 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5330 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005331 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005332 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005333
5334 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5335 goto error;
5336
5337 {
5338 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\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 }
5342
Christopher Faulet61cc8522020-04-20 14:54:42 +02005343 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5344 goto error;
5345
5346 {
5347 const char * const strs[2] = { "DATA\r\n" };
5348 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005349 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005350 }
5351
5352 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5353 goto error;
5354
5355 {
5356 struct tm tm;
5357 char datestr[48];
5358 const char * const strs[18] = {
5359 "From: ", p->email_alert.from, "\r\n",
5360 "To: ", p->email_alert.to, "\r\n",
5361 "Date: ", datestr, "\r\n",
5362 "Subject: [HAproxy Alert] ", msg, "\r\n",
5363 "\r\n",
5364 msg, "\r\n",
5365 "\r\n",
5366 ".\r\n",
5367 NULL
5368 };
5369
5370 get_localtime(date.tv_sec, &tm);
5371
5372 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005373 goto error;
5374 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005375
5376 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005377 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005378 }
5379
5380 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005381 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005382
5383 {
5384 const char * const strs[2] = { "QUIT\r\n" };
5385 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5386 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005387 }
5388
Christopher Faulet61cc8522020-04-20 14:54:42 +02005389 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5390 goto error;
5391
5392 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5393 task_wakeup(check->task, TASK_WOKEN_MSG);
5394 LIST_ADDQ(&q->email_alerts, &alert->list);
5395 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5396 return 1;
5397
5398error:
5399 email_alert_free(alert);
5400 return 0;
5401}
5402
5403static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5404{
5405 int i;
5406 struct mailer *mailer;
5407
5408 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5409 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5410 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5411 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5412 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005413 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005414 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005415
Christopher Faulet61cc8522020-04-20 14:54:42 +02005416 return;
5417}
5418
5419/*
5420 * Send email alert if configured.
5421 */
5422void send_email_alert(struct server *s, int level, const char *format, ...)
5423{
5424 va_list argp;
5425 char buf[1024];
5426 int len;
5427 struct proxy *p = s->proxy;
5428
5429 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5430 return;
5431
5432 va_start(argp, format);
5433 len = vsnprintf(buf, sizeof(buf), format, argp);
5434 va_end(argp);
5435
5436 if (len < 0 || len >= sizeof(buf)) {
5437 ha_alert("Email alert [%s] could not format message\n", p->id);
5438 return;
5439 }
5440
5441 enqueue_email_alert(p, s, buf);
5442}
5443
5444/**************************************************************************/
5445/************************** Check sample fetches **************************/
5446/**************************************************************************/
5447/* extracts check payload at a fixed position and length */
5448static int
5449smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5450{
5451 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5452 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5453 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5454 struct buffer *buf;
5455
5456 if (!check)
5457 return 0;
5458
5459 buf = &check->bi;
5460 if (buf_offset > b_data(buf))
5461 goto no_match;
5462 if (buf_offset + buf_size > b_data(buf))
5463 buf_size = 0;
5464
5465 /* init chunk as read only */
5466 smp->data.type = SMP_T_STR;
5467 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5468 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5469
5470 return 1;
5471
5472 no_match:
5473 smp->flags = 0;
5474 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005475}
5476
Christopher Faulet61cc8522020-04-20 14:54:42 +02005477static struct sample_fetch_kw_list smp_kws = {ILH, {
5478 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5479 { /* END */ },
5480}};
5481
5482INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5483
5484
5485/**************************************************************************/
5486/************************ Check's parsing functions ***********************/
5487/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005488/* Parses the "tcp-check" proxy keyword */
5489static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5490 struct proxy *defpx, const char *file, int line,
5491 char **errmsg)
5492{
Christopher Faulet404f9192020-04-09 23:13:54 +02005493 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005494 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005495 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005496
5497 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5498 ret = 1;
5499
Christopher Faulet404f9192020-04-09 23:13:54 +02005500 /* Deduce the ruleset name from the proxy info */
5501 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5502 ((curpx == defpx) ? "defaults" : curpx->id),
5503 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005504
Christopher Faulet61cc8522020-04-20 14:54:42 +02005505 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005506 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005507 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005508 if (rs == NULL) {
5509 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005510 goto error;
5511 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005512 }
5513
Gaetan Rivet5301b012020-02-25 17:19:17 +01005514 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005515 if (!LIST_ISEMPTY(&rs->rules)) {
5516 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005517 index = chk->index + 1;
5518 }
5519
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005520 cur_arg = 1;
5521 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005522 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005523 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005524 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005525 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005526 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005527 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005528 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005529 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005530 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5531
5532 if (!kw) {
5533 action_kw_tcp_check_build_list(&trash);
5534 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5535 "%s%s. but got '%s'",
5536 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5537 goto error;
5538 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005539 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005540 }
5541
5542 if (!chk) {
5543 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5544 goto error;
5545 }
5546 ret = (*errmsg != NULL); /* Handle warning */
5547
5548 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005549 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005550 LIST_ADDQ(&rs->rules, &chk->list);
5551
5552 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005553 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005554 /* Use this ruleset if the proxy already has tcp-check enabled */
5555 curpx->tcpcheck_rules.list = &rs->rules;
5556 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5557 }
5558 else {
5559 /* mark this ruleset as unused for now */
5560 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5561 }
5562
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005563 return ret;
5564
5565 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005566 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005567 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005568 return -1;
5569}
5570
Christopher Faulet51b129f2020-04-09 15:54:18 +02005571/* Parses the "http-check" proxy keyword */
5572static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5573 struct proxy *defpx, const char *file, int line,
5574 char **errmsg)
5575{
Christopher Faulete5870d82020-04-15 11:32:03 +02005576 struct tcpcheck_ruleset *rs = NULL;
5577 struct tcpcheck_rule *chk = NULL;
5578 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005579
5580 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5581 ret = 1;
5582
5583 cur_arg = 1;
5584 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5585 /* enable a graceful server shutdown on an HTTP 404 response */
5586 curpx->options |= PR_O_DISABLE404;
5587 if (too_many_args(1, args, errmsg, NULL))
5588 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005589 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005590 }
5591 else if (strcmp(args[cur_arg], "send-state") == 0) {
5592 /* enable emission of the apparent state of a server in HTTP checks */
5593 curpx->options2 |= PR_O2_CHK_SNDST;
5594 if (too_many_args(1, args, errmsg, NULL))
5595 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005596 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005597 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005598
Christopher Faulete5870d82020-04-15 11:32:03 +02005599 /* Deduce the ruleset name from the proxy info */
5600 chunk_printf(&trash, "*http-check-%s_%s-%d",
5601 ((curpx == defpx) ? "defaults" : curpx->id),
5602 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005603
Christopher Faulet61cc8522020-04-20 14:54:42 +02005604 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005605 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005606 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005607 if (rs == NULL) {
5608 memprintf(errmsg, "out of memory.\n");
5609 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005610 }
5611 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005612
Christopher Faulete5870d82020-04-15 11:32:03 +02005613 index = 0;
5614 if (!LIST_ISEMPTY(&rs->rules)) {
5615 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5616 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5617 index = chk->index + 1;
5618 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005619
Christopher Faulete5870d82020-04-15 11:32:03 +02005620 if (strcmp(args[cur_arg], "connect") == 0)
5621 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5622 else if (strcmp(args[cur_arg], "send") == 0)
5623 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5624 else if (strcmp(args[cur_arg], "expect") == 0)
5625 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5626 file, line, errmsg);
5627 else if (strcmp(args[cur_arg], "comment") == 0)
5628 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5629 else {
5630 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005631
Christopher Faulete5870d82020-04-15 11:32:03 +02005632 if (!kw) {
5633 action_kw_tcp_check_build_list(&trash);
5634 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5635 " 'send', 'expect'%s%s. but got '%s'",
5636 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5637 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005638 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005639 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5640 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005641
Christopher Faulete5870d82020-04-15 11:32:03 +02005642 if (!chk) {
5643 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5644 goto error;
5645 }
5646 ret = (*errmsg != NULL); /* Handle warning */
5647
5648 chk->index = index;
5649 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5650 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5651 /* Use this ruleset if the proxy already has http-check enabled */
5652 curpx->tcpcheck_rules.list = &rs->rules;
5653 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5654 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5655 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5656 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005657 goto error;
5658 }
5659 }
5660 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005661 /* mark this ruleset as unused for now */
5662 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5663 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005664 }
5665
Christopher Faulete5870d82020-04-15 11:32:03 +02005666 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005667 return ret;
5668
5669 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005670 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005671 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005672 return -1;
5673}
5674
Christopher Faulete9111b62020-04-09 18:12:08 +02005675/* Parses the "external-check" proxy keyword */
5676static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5677 struct proxy *defpx, const char *file, int line,
5678 char **errmsg)
5679{
5680 int cur_arg, ret = 0;
5681
5682 cur_arg = 1;
5683 if (!*(args[cur_arg])) {
5684 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5685 goto error;
5686 }
5687
5688 if (strcmp(args[cur_arg], "command") == 0) {
5689 if (too_many_args(2, args, errmsg, NULL))
5690 goto error;
5691 if (!*(args[cur_arg+1])) {
5692 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5693 goto error;
5694 }
5695 free(curpx->check_command);
5696 curpx->check_command = strdup(args[cur_arg+1]);
5697 }
5698 else if (strcmp(args[cur_arg], "path") == 0) {
5699 if (too_many_args(2, args, errmsg, NULL))
5700 goto error;
5701 if (!*(args[cur_arg+1])) {
5702 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5703 goto error;
5704 }
5705 free(curpx->check_path);
5706 curpx->check_path = strdup(args[cur_arg+1]);
5707 }
5708 else {
5709 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5710 args[0], args[1]);
5711 goto error;
5712 }
5713
5714 ret = (*errmsg != NULL); /* Handle warning */
5715 return ret;
5716
5717error:
5718 return -1;
5719}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005720
Christopher Faulet430e4802020-04-09 15:28:16 +02005721/* Parses the "option tcp-check" proxy keyword */
5722int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5723 const char *file, int line)
5724{
Christopher Faulet404f9192020-04-09 23:13:54 +02005725 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005726 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5727 int err_code = 0;
5728
5729 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5730 err_code |= ERR_WARN;
5731
5732 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5733 goto out;
5734
Christopher Faulet404f9192020-04-09 23:13:54 +02005735 curpx->options2 &= ~PR_O2_CHK_ANY;
5736 curpx->options2 |= PR_O2_TCPCHK_CHK;
5737
Christopher Fauletd7e63962020-04-17 20:15:59 +02005738 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005739 /* If a tcp-check rulesset is already set, do nothing */
5740 if (rules->list)
5741 goto out;
5742
5743 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5744 * get it.
5745 */
5746 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5747 goto curpx_ruleset;
5748
5749 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5750 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005751 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005752 if (rs)
5753 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005754 }
5755
Christopher Faulet404f9192020-04-09 23:13:54 +02005756 curpx_ruleset:
5757 /* Deduce the ruleset name from the proxy info */
5758 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5759 ((curpx == defpx) ? "defaults" : curpx->id),
5760 curpx->conf.file, curpx->conf.line);
5761
Christopher Faulet61cc8522020-04-20 14:54:42 +02005762 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005763 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005764 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005765 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005766 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5767 goto error;
5768 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005769 }
5770
Christopher Faulet404f9192020-04-09 23:13:54 +02005771 ruleset_found:
5772 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005773 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005774 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005775
5776 out:
5777 return err_code;
5778
5779 error:
5780 err_code |= ERR_ALERT | ERR_FATAL;
5781 goto out;
5782}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005783
5784/* Parses the "option redis-check" proxy keyword */
5785int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5786 const char *file, int line)
5787{
5788 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5789 static char *redis_res = "+PONG\r\n";
5790
5791 struct tcpcheck_ruleset *rs = NULL;
5792 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5793 struct tcpcheck_rule *chk;
5794 char *errmsg = NULL;
5795 int err_code = 0;
5796
5797 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5798 err_code |= ERR_WARN;
5799
5800 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5801 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005802
5803 curpx->options2 &= ~PR_O2_CHK_ANY;
5804 curpx->options2 |= PR_O2_TCPCHK_CHK;
5805
5806 free_tcpcheck_vars(&rules->preset_vars);
5807 rules->list = NULL;
5808 rules->flags = 0;
5809
Christopher Faulet61cc8522020-04-20 14:54:42 +02005810 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005811 if (rs)
5812 goto ruleset_found;
5813
Christopher Faulet61cc8522020-04-20 14:54:42 +02005814 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005815 if (rs == NULL) {
5816 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5817 goto error;
5818 }
5819
5820 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5821 1, curpx, &rs->rules, file, line, &errmsg);
5822 if (!chk) {
5823 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5824 goto error;
5825 }
5826 chk->index = 0;
5827 LIST_ADDQ(&rs->rules, &chk->list);
5828
5829 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5830 "error-status", "L7STS",
5831 "on-error", "%[check.payload(),cut_crlf]",
5832 "on-success", "Redis server is ok",
5833 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005834 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005835 if (!chk) {
5836 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5837 goto error;
5838 }
5839 chk->index = 1;
5840 LIST_ADDQ(&rs->rules, &chk->list);
5841
Christopher Fauletd7cee712020-04-21 13:45:00 +02005842 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005843
5844 ruleset_found:
5845 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005846 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005847
5848 out:
5849 free(errmsg);
5850 return err_code;
5851
5852 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005853 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005854 err_code |= ERR_ALERT | ERR_FATAL;
5855 goto out;
5856}
5857
Christopher Faulet811f78c2020-04-01 11:10:27 +02005858
5859/* Parses the "option ssl-hello-chk" proxy keyword */
5860int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5861 const char *file, int line)
5862{
5863 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5864 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5865 *
5866 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5867 */
5868 static char sslv3_client_hello[] = {
5869 "16" /* ContentType : 0x16 = Hanshake */
5870 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5871 "0079" /* ContentLength : 0x79 bytes after this one */
5872 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5873 "000075" /* HandshakeLength : 0x75 bytes after this one */
5874 "0300" /* Hello Version : 0x0300 = v3 */
5875 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5876 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5877 "00" /* Session ID length : empty (no session ID) */
5878 "004E" /* Cipher Suite Length : 78 bytes after this one */
5879 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5880 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5881 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5882 "000D" "000E" "000F" "0010" /* various bit lengths, */
5883 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5884 "0015" "0016" "0017" "0018"
5885 "0019" "001A" "001B" "002F"
5886 "0030" "0031" "0032" "0033"
5887 "0034" "0035" "0036" "0037"
5888 "0038" "0039" "003A"
5889 "01" /* Compression Length : 0x01 = 1 byte for types */
5890 "00" /* Compression Type : 0x00 = NULL compression */
5891 };
5892
5893 struct tcpcheck_ruleset *rs = NULL;
5894 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5895 struct tcpcheck_rule *chk;
5896 char *errmsg = NULL;
5897 int err_code = 0;
5898
5899 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5900 err_code |= ERR_WARN;
5901
5902 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5903 goto out;
5904
Christopher Faulet811f78c2020-04-01 11:10:27 +02005905 curpx->options2 &= ~PR_O2_CHK_ANY;
5906 curpx->options2 |= PR_O2_TCPCHK_CHK;
5907
5908 free_tcpcheck_vars(&rules->preset_vars);
5909 rules->list = NULL;
5910 rules->flags = 0;
5911
Christopher Faulet61cc8522020-04-20 14:54:42 +02005912 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005913 if (rs)
5914 goto ruleset_found;
5915
Christopher Faulet61cc8522020-04-20 14:54:42 +02005916 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005917 if (rs == NULL) {
5918 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5919 goto error;
5920 }
5921
5922 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5923 1, curpx, &rs->rules, file, line, &errmsg);
5924 if (!chk) {
5925 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5926 goto error;
5927 }
5928 chk->index = 0;
5929 LIST_ADDQ(&rs->rules, &chk->list);
5930
5931 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005932 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005933 "error-status", "L6RSP", "tout-status", "L6TOUT",
5934 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005935 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005936 if (!chk) {
5937 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5938 goto error;
5939 }
5940 chk->index = 1;
5941 LIST_ADDQ(&rs->rules, &chk->list);
5942
Christopher Fauletd7cee712020-04-21 13:45:00 +02005943 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005944
5945 ruleset_found:
5946 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005947 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005948
5949 out:
5950 free(errmsg);
5951 return err_code;
5952
5953 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005954 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005955 err_code |= ERR_ALERT | ERR_FATAL;
5956 goto out;
5957}
5958
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005959/* Parses the "option smtpchk" proxy keyword */
5960int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5961 const char *file, int line)
5962{
5963 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5964
5965 struct tcpcheck_ruleset *rs = NULL;
5966 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5967 struct tcpcheck_rule *chk;
5968 struct tcpcheck_var *var = NULL;
5969 char *cmd = NULL, *errmsg = NULL;
5970 int err_code = 0;
5971
5972 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5973 err_code |= ERR_WARN;
5974
5975 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5976 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005977
5978 curpx->options2 &= ~PR_O2_CHK_ANY;
5979 curpx->options2 |= PR_O2_TCPCHK_CHK;
5980
5981 free_tcpcheck_vars(&rules->preset_vars);
5982 rules->list = NULL;
5983 rules->flags = 0;
5984
5985 cur_arg += 2;
5986 if (*args[cur_arg] && *args[cur_arg+1] &&
5987 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5988 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5989 if (cmd)
5990 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5991 }
5992 else {
5993 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5994 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5995 cmd = strdup("HELO localhost");
5996 }
5997
Christopher Fauletb61caf42020-04-21 10:57:42 +02005998 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005999 if (cmd == NULL || var == NULL) {
6000 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6001 goto error;
6002 }
6003 var->data.type = SMP_T_STR;
6004 var->data.u.str.area = cmd;
6005 var->data.u.str.data = strlen(cmd);
6006 LIST_INIT(&var->list);
6007 LIST_ADDQ(&rules->preset_vars, &var->list);
6008 cmd = NULL;
6009 var = NULL;
6010
Christopher Faulet61cc8522020-04-20 14:54:42 +02006011 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006012 if (rs)
6013 goto ruleset_found;
6014
Christopher Faulet61cc8522020-04-20 14:54:42 +02006015 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006016 if (rs == NULL) {
6017 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6018 goto error;
6019 }
6020
6021 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6022 1, curpx, &rs->rules, file, line, &errmsg);
6023 if (!chk) {
6024 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6025 goto error;
6026 }
6027 chk->index = 0;
6028 LIST_ADDQ(&rs->rules, &chk->list);
6029
6030 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6031 "min-recv", "4",
6032 "error-status", "L7RSP",
6033 "on-error", "%[check.payload(),cut_crlf]",
6034 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006035 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006036 if (!chk) {
6037 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6038 goto error;
6039 }
6040 chk->index = 1;
6041 LIST_ADDQ(&rs->rules, &chk->list);
6042
6043 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6044 "min-recv", "4",
6045 "error-status", "L7STS",
6046 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6047 "status-code", "check.payload(0,3)",
6048 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006049 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006050 if (!chk) {
6051 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6052 goto error;
6053 }
6054 chk->index = 2;
6055 LIST_ADDQ(&rs->rules, &chk->list);
6056
6057 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6058 1, curpx, &rs->rules, file, line, &errmsg);
6059 if (!chk) {
6060 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6061 goto error;
6062 }
6063 chk->index = 3;
6064 LIST_ADDQ(&rs->rules, &chk->list);
6065
6066 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6067 "min-recv", "4",
6068 "error-status", "L7STS",
6069 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6070 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6071 "status-code", "check.payload(0,3)",
6072 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006073 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006074 if (!chk) {
6075 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6076 goto error;
6077 }
6078 chk->index = 4;
6079 LIST_ADDQ(&rs->rules, &chk->list);
6080
Christopher Fauletd7cee712020-04-21 13:45:00 +02006081 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006082
6083 ruleset_found:
6084 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006085 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006086
6087 out:
6088 free(errmsg);
6089 return err_code;
6090
6091 error:
6092 free(cmd);
6093 free(var);
6094 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006095 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006096 err_code |= ERR_ALERT | ERR_FATAL;
6097 goto out;
6098}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006099
Christopher Fauletce355072020-04-02 11:44:39 +02006100/* Parses the "option pgsql-check" proxy keyword */
6101int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6102 const char *file, int line)
6103{
6104 static char pgsql_req[] = {
6105 "%[var(check.plen),htonl,hex]" /* The packet length*/
6106 "00030000" /* the version 3.0 */
6107 "7573657200" /* "user" key */
6108 "%[var(check.username),hex]00" /* the username */
6109 "00"
6110 };
6111
6112 struct tcpcheck_ruleset *rs = NULL;
6113 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6114 struct tcpcheck_rule *chk;
6115 struct tcpcheck_var *var = NULL;
6116 char *user = NULL, *errmsg = NULL;
6117 size_t packetlen = 0;
6118 int err_code = 0;
6119
6120 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6121 err_code |= ERR_WARN;
6122
6123 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6124 goto out;
6125
Christopher Fauletce355072020-04-02 11:44:39 +02006126 curpx->options2 &= ~PR_O2_CHK_ANY;
6127 curpx->options2 |= PR_O2_TCPCHK_CHK;
6128
6129 free_tcpcheck_vars(&rules->preset_vars);
6130 rules->list = NULL;
6131 rules->flags = 0;
6132
6133 cur_arg += 2;
6134 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6135 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6136 file, line, args[0], args[1]);
6137 goto error;
6138 }
6139 if (strcmp(args[cur_arg], "user") == 0) {
6140 packetlen = 15 + strlen(args[cur_arg+1]);
6141 user = strdup(args[cur_arg+1]);
6142
Christopher Fauletb61caf42020-04-21 10:57:42 +02006143 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006144 if (user == NULL || var == NULL) {
6145 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6146 goto error;
6147 }
6148 var->data.type = SMP_T_STR;
6149 var->data.u.str.area = user;
6150 var->data.u.str.data = strlen(user);
6151 LIST_INIT(&var->list);
6152 LIST_ADDQ(&rules->preset_vars, &var->list);
6153 user = NULL;
6154 var = NULL;
6155
Christopher Fauletb61caf42020-04-21 10:57:42 +02006156 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006157 if (var == NULL) {
6158 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6159 goto error;
6160 }
6161 var->data.type = SMP_T_SINT;
6162 var->data.u.sint = packetlen;
6163 LIST_INIT(&var->list);
6164 LIST_ADDQ(&rules->preset_vars, &var->list);
6165 var = NULL;
6166 }
6167 else {
6168 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6169 file, line, args[0], args[1]);
6170 goto error;
6171 }
6172
Christopher Faulet61cc8522020-04-20 14:54:42 +02006173 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006174 if (rs)
6175 goto ruleset_found;
6176
Christopher Faulet61cc8522020-04-20 14:54:42 +02006177 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006178 if (rs == NULL) {
6179 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6180 goto error;
6181 }
6182
6183 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6184 1, curpx, &rs->rules, file, line, &errmsg);
6185 if (!chk) {
6186 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6187 goto error;
6188 }
6189 chk->index = 0;
6190 LIST_ADDQ(&rs->rules, &chk->list);
6191
6192 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
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 = 1;
6199 LIST_ADDQ(&rs->rules, &chk->list);
6200
6201 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6202 "min-recv", "5",
6203 "error-status", "L7RSP",
6204 "on-error", "%[check.payload(6,0)]",
6205 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006206 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006207 if (!chk) {
6208 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6209 goto error;
6210 }
6211 chk->index = 2;
6212 LIST_ADDQ(&rs->rules, &chk->list);
6213
6214 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6215 "min-recv", "9",
6216 "error-status", "L7STS",
6217 "on-success", "PostgreSQL server is ok",
6218 "on-error", "PostgreSQL unknown error",
6219 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006220 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006221 if (!chk) {
6222 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6223 goto error;
6224 }
6225 chk->index = 3;
6226 LIST_ADDQ(&rs->rules, &chk->list);
6227
Christopher Fauletd7cee712020-04-21 13:45:00 +02006228 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006229
6230 ruleset_found:
6231 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006232 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006233
6234 out:
6235 free(errmsg);
6236 return err_code;
6237
6238 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006239 free(user);
6240 free(var);
6241 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006242 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006243 err_code |= ERR_ALERT | ERR_FATAL;
6244 goto out;
6245}
6246
6247
6248/* Parses the "option mysql-check" proxy keyword */
6249int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6250 const char *file, int line)
6251{
6252 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6253 * const char mysql40_client_auth_pkt[] = {
6254 * "\x0e\x00\x00" // packet length
6255 * "\x01" // packet number
6256 * "\x00\x00" // client capabilities
6257 * "\x00\x00\x01" // max packet
6258 * "haproxy\x00" // username (null terminated string)
6259 * "\x00" // filler (always 0x00)
6260 * "\x01\x00\x00" // packet length
6261 * "\x00" // packet number
6262 * "\x01" // COM_QUIT command
6263 * };
6264 */
6265 static char mysql40_rsname[] = "*mysql40-check";
6266 static char mysql40_req[] = {
6267 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6268 "0080" /* client capabilities */
6269 "000001" /* max packet */
6270 "%[var(check.username),hex]00" /* the username */
6271 "00" /* filler (always 0x00) */
6272 "010000" /* packet length*/
6273 "00" /* sequence ID */
6274 "01" /* COM_QUIT command */
6275 };
6276
6277 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6278 * const char mysql41_client_auth_pkt[] = {
6279 * "\x0e\x00\x00\" // packet length
6280 * "\x01" // packet number
6281 * "\x00\x00\x00\x00" // client capabilities
6282 * "\x00\x00\x00\x01" // max packet
6283 * "\x21" // character set (UTF-8)
6284 * char[23] // All zeroes
6285 * "haproxy\x00" // username (null terminated string)
6286 * "\x00" // filler (always 0x00)
6287 * "\x01\x00\x00" // packet length
6288 * "\x00" // packet number
6289 * "\x01" // COM_QUIT command
6290 * };
6291 */
6292 static char mysql41_rsname[] = "*mysql41-check";
6293 static char mysql41_req[] = {
6294 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6295 "00820000" /* client capabilities */
6296 "00800001" /* max packet */
6297 "21" /* character set (UTF-8) */
6298 "000000000000000000000000" /* 23 bytes, al zeroes */
6299 "0000000000000000000000"
6300 "%[var(check.username),hex]00" /* the username */
6301 "00" /* filler (always 0x00) */
6302 "010000" /* packet length*/
6303 "00" /* sequence ID */
6304 "01" /* COM_QUIT command */
6305 };
6306
6307 struct tcpcheck_ruleset *rs = NULL;
6308 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6309 struct tcpcheck_rule *chk;
6310 struct tcpcheck_var *var = NULL;
6311 char *mysql_rsname = "*mysql-check";
6312 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6313 int index = 0, err_code = 0;
6314
6315 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6316 err_code |= ERR_WARN;
6317
6318 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6319 goto out;
6320
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006321 curpx->options2 &= ~PR_O2_CHK_ANY;
6322 curpx->options2 |= PR_O2_TCPCHK_CHK;
6323
6324 free_tcpcheck_vars(&rules->preset_vars);
6325 rules->list = NULL;
6326 rules->flags = 0;
6327
6328 cur_arg += 2;
6329 if (*args[cur_arg]) {
6330 char *user;
6331 int packetlen, userlen;
6332
6333 if (strcmp(args[cur_arg], "user") != 0) {
6334 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6335 file, line, args[0], args[1], args[cur_arg]);
6336 goto error;
6337 }
6338
6339 if (*(args[cur_arg+1]) == 0) {
6340 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6341 file, line, args[0], args[1], args[cur_arg]);
6342 goto error;
6343 }
6344
6345 hdr = calloc(4, sizeof(*hdr));
6346 user = strdup(args[cur_arg+1]);
6347 userlen = strlen(args[cur_arg+1]);
6348
6349 if (hdr == NULL || user == NULL) {
6350 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6351 goto error;
6352 }
6353
6354 if (*args[cur_arg+2]) {
6355 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6356 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6357 file, line, args[cur_arg], args[cur_arg+2]);
6358 goto error;
6359 }
6360 packetlen = userlen + 7 + 27;
6361 mysql_req = mysql41_req;
6362 mysql_rsname = mysql41_rsname;
6363 }
6364 else {
6365 packetlen = userlen + 7;
6366 mysql_req = mysql40_req;
6367 mysql_rsname = mysql40_rsname;
6368 }
6369
6370 hdr[0] = (unsigned char)(packetlen & 0xff);
6371 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6372 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6373 hdr[3] = 1;
6374
Christopher Fauletb61caf42020-04-21 10:57:42 +02006375 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006376 if (var == NULL) {
6377 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6378 goto error;
6379 }
6380 var->data.type = SMP_T_STR;
6381 var->data.u.str.area = hdr;
6382 var->data.u.str.data = 4;
6383 LIST_INIT(&var->list);
6384 LIST_ADDQ(&rules->preset_vars, &var->list);
6385 hdr = NULL;
6386 var = NULL;
6387
Christopher Fauletb61caf42020-04-21 10:57:42 +02006388 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006389 if (var == NULL) {
6390 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6391 goto error;
6392 }
6393 var->data.type = SMP_T_STR;
6394 var->data.u.str.area = user;
6395 var->data.u.str.data = strlen(user);
6396 LIST_INIT(&var->list);
6397 LIST_ADDQ(&rules->preset_vars, &var->list);
6398 user = NULL;
6399 var = NULL;
6400 }
6401
Christopher Faulet61cc8522020-04-20 14:54:42 +02006402 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006403 if (rs)
6404 goto ruleset_found;
6405
Christopher Faulet61cc8522020-04-20 14:54:42 +02006406 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006407 if (rs == NULL) {
6408 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6409 goto error;
6410 }
6411
6412 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6413 1, curpx, &rs->rules, file, line, &errmsg);
6414 if (!chk) {
6415 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6416 goto error;
6417 }
6418 chk->index = index++;
6419 LIST_ADDQ(&rs->rules, &chk->list);
6420
6421 if (mysql_req) {
6422 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6423 1, curpx, &rs->rules, file, line, &errmsg);
6424 if (!chk) {
6425 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6426 goto error;
6427 }
6428 chk->index = index++;
6429 LIST_ADDQ(&rs->rules, &chk->list);
6430 }
6431
6432 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006433 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006434 if (!chk) {
6435 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6436 goto error;
6437 }
6438 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6439 chk->index = index++;
6440 LIST_ADDQ(&rs->rules, &chk->list);
6441
6442 if (mysql_req) {
6443 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006444 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006445 if (!chk) {
6446 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6447 goto error;
6448 }
6449 chk->expect.custom = tcpcheck_mysql_expect_ok;
6450 chk->index = index++;
6451 LIST_ADDQ(&rs->rules, &chk->list);
6452 }
6453
Christopher Fauletd7cee712020-04-21 13:45:00 +02006454 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006455
6456 ruleset_found:
6457 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006458 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006459
6460 out:
6461 free(errmsg);
6462 return err_code;
6463
6464 error:
6465 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006466 free(user);
6467 free(var);
6468 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006469 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006470 err_code |= ERR_ALERT | ERR_FATAL;
6471 goto out;
6472}
6473
Christopher Faulet1997eca2020-04-03 23:13:50 +02006474int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6475 const char *file, int line)
6476{
6477 static char *ldap_req = "300C020101600702010304008000";
6478
6479 struct tcpcheck_ruleset *rs = NULL;
6480 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6481 struct tcpcheck_rule *chk;
6482 char *errmsg = NULL;
6483 int err_code = 0;
6484
6485 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6486 err_code |= ERR_WARN;
6487
6488 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6489 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006490
6491 curpx->options2 &= ~PR_O2_CHK_ANY;
6492 curpx->options2 |= PR_O2_TCPCHK_CHK;
6493
6494 free_tcpcheck_vars(&rules->preset_vars);
6495 rules->list = NULL;
6496 rules->flags = 0;
6497
Christopher Faulet61cc8522020-04-20 14:54:42 +02006498 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006499 if (rs)
6500 goto ruleset_found;
6501
Christopher Faulet61cc8522020-04-20 14:54:42 +02006502 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006503 if (rs == NULL) {
6504 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6505 goto error;
6506 }
6507
6508 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6509 1, curpx, &rs->rules, file, line, &errmsg);
6510 if (!chk) {
6511 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6512 goto error;
6513 }
6514 chk->index = 0;
6515 LIST_ADDQ(&rs->rules, &chk->list);
6516
6517 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6518 "min-recv", "14",
6519 "on-error", "Not LDAPv3 protocol",
6520 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006521 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006522 if (!chk) {
6523 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6524 goto error;
6525 }
6526 chk->index = 1;
6527 LIST_ADDQ(&rs->rules, &chk->list);
6528
6529 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006530 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006531 if (!chk) {
6532 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6533 goto error;
6534 }
6535 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6536 chk->index = 2;
6537 LIST_ADDQ(&rs->rules, &chk->list);
6538
Christopher Fauletd7cee712020-04-21 13:45:00 +02006539 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006540
6541 ruleset_found:
6542 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006543 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006544
6545 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006546 free(errmsg);
6547 return err_code;
6548
6549 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006550 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006551 err_code |= ERR_ALERT | ERR_FATAL;
6552 goto out;
6553}
6554
6555int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6556 const char *file, int line)
6557{
6558 struct tcpcheck_ruleset *rs = NULL;
6559 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6560 struct tcpcheck_rule *chk;
6561 char *spop_req = NULL;
6562 char *errmsg = NULL;
6563 int spop_len = 0, err_code = 0;
6564
6565 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6566 err_code |= ERR_WARN;
6567
6568 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6569 goto out;
6570
Christopher Faulet267b01b2020-04-04 10:27:09 +02006571 curpx->options2 &= ~PR_O2_CHK_ANY;
6572 curpx->options2 |= PR_O2_TCPCHK_CHK;
6573
6574 free_tcpcheck_vars(&rules->preset_vars);
6575 rules->list = NULL;
6576 rules->flags = 0;
6577
6578
Christopher Faulet61cc8522020-04-20 14:54:42 +02006579 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006580 if (rs)
6581 goto ruleset_found;
6582
Christopher Faulet61cc8522020-04-20 14:54:42 +02006583 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006584 if (rs == NULL) {
6585 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6586 goto error;
6587 }
6588
6589 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6590 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6591 goto error;
6592 }
6593 chunk_reset(&trash);
6594 dump_binary(&trash, spop_req, spop_len);
6595 trash.area[trash.data] = '\0';
6596
6597 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6598 1, curpx, &rs->rules, file, line, &errmsg);
6599 if (!chk) {
6600 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6601 goto error;
6602 }
6603 chk->index = 0;
6604 LIST_ADDQ(&rs->rules, &chk->list);
6605
6606 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006607 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006608 if (!chk) {
6609 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6610 goto error;
6611 }
6612 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6613 chk->index = 1;
6614 LIST_ADDQ(&rs->rules, &chk->list);
6615
Christopher Fauletd7cee712020-04-21 13:45:00 +02006616 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006617
6618 ruleset_found:
6619 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006620 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006621
6622 out:
6623 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006624 free(errmsg);
6625 return err_code;
6626
6627 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006628 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006629 err_code |= ERR_ALERT | ERR_FATAL;
6630 goto out;
6631}
Christopher Fauletce355072020-04-02 11:44:39 +02006632
Christopher Faulete5870d82020-04-15 11:32:03 +02006633
6634struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6635{
6636 struct tcpcheck_rule *chk = NULL;
6637 struct tcpcheck_http_hdr *hdr = NULL;
6638 char *meth = NULL, *uri = NULL, *vsn = NULL;
6639 char *hdrs, *body;
6640
6641 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6642 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6643 if (hdrs == body)
6644 hdrs = NULL;
6645 if (hdrs) {
6646 *hdrs = '\0';
6647 hdrs +=2;
6648 }
6649 if (body) {
6650 *body = '\0';
6651 body += 4;
6652 }
6653 if (hdrs || body) {
6654 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6655 " Please, consider to use 'http-check send' directive instead.");
6656 }
6657
6658 chk = calloc(1, sizeof(*chk));
6659 if (!chk) {
6660 memprintf(errmsg, "out of memory");
6661 goto error;
6662 }
6663 chk->action = TCPCHK_ACT_SEND;
6664 chk->send.type = TCPCHK_SEND_HTTP;
6665 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6666 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6667 LIST_INIT(&chk->send.http.hdrs);
6668
6669 /* Copy the method, uri and version */
6670 if (*args[cur_arg]) {
6671 if (!*args[cur_arg+1])
6672 uri = args[cur_arg];
6673 else
6674 meth = args[cur_arg];
6675 }
6676 if (*args[cur_arg+1])
6677 uri = args[cur_arg+1];
6678 if (*args[cur_arg+2])
6679 vsn = args[cur_arg+2];
6680
6681 if (meth) {
6682 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6683 chk->send.http.meth.str.area = strdup(meth);
6684 chk->send.http.meth.str.data = strlen(meth);
6685 if (!chk->send.http.meth.str.area) {
6686 memprintf(errmsg, "out of memory");
6687 goto error;
6688 }
6689 }
6690 if (uri) {
6691 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006692 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006693 memprintf(errmsg, "out of memory");
6694 goto error;
6695 }
6696 }
6697 if (vsn) {
6698 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006699 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006700 memprintf(errmsg, "out of memory");
6701 goto error;
6702 }
6703 }
6704
6705 /* Copy the header */
6706 if (hdrs) {
6707 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6708 struct h1m h1m;
6709 int i, ret;
6710
6711 /* Build and parse the request */
6712 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6713
6714 h1m.flags = H1_MF_HDRS_ONLY;
6715 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6716 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6717 &h1m, NULL);
6718 if (ret <= 0) {
6719 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6720 goto error;
6721 }
6722
Christopher Fauletb61caf42020-04-21 10:57:42 +02006723 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006724 hdr = calloc(1, sizeof(*hdr));
6725 if (!hdr) {
6726 memprintf(errmsg, "out of memory");
6727 goto error;
6728 }
6729 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006730 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006731 if (!hdr->name.ptr) {
6732 memprintf(errmsg, "out of memory");
6733 goto error;
6734 }
6735
Christopher Fauletb61caf42020-04-21 10:57:42 +02006736 ist0(tmp_hdrs[i].v);
6737 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 +02006738 goto error;
6739 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6740 }
6741 }
6742
6743 /* Copy the body */
6744 if (body) {
6745 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006746 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006747 memprintf(errmsg, "out of memory");
6748 goto error;
6749 }
6750 }
6751
6752 return chk;
6753
6754 error:
6755 free_tcpcheck_http_hdr(hdr);
6756 free_tcpcheck(chk, 0);
6757 return NULL;
6758}
6759
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006760int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6761 const char *file, int line)
6762{
Christopher Faulete5870d82020-04-15 11:32:03 +02006763 struct tcpcheck_ruleset *rs = NULL;
6764 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6765 struct tcpcheck_rule *chk;
6766 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006767 int err_code = 0;
6768
6769 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6770 err_code |= ERR_WARN;
6771
6772 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6773 goto out;
6774
Christopher Faulete5870d82020-04-15 11:32:03 +02006775 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6776 if (!chk) {
6777 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6778 goto error;
6779 }
6780 if (errmsg) {
6781 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6782 err_code |= ERR_WARN;
6783 free(errmsg);
6784 errmsg = NULL;
6785 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006786
Christopher Faulete5870d82020-04-15 11:32:03 +02006787 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006788 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006789 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006790
Christopher Faulete5870d82020-04-15 11:32:03 +02006791 free_tcpcheck_vars(&rules->preset_vars);
6792 rules->list = NULL;
6793 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006794
Christopher Faulete5870d82020-04-15 11:32:03 +02006795 /* Deduce the ruleset name from the proxy info */
6796 chunk_printf(&trash, "*http-check-%s_%s-%d",
6797 ((curpx == defpx) ? "defaults" : curpx->id),
6798 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006799
Christopher Faulet61cc8522020-04-20 14:54:42 +02006800 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006801 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006802 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006803 if (rs == NULL) {
6804 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6805 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006806 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006807 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006808
Christopher Faulete5870d82020-04-15 11:32:03 +02006809 rules->list = &rs->rules;
6810 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6811 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6812 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6813 rules->list = NULL;
6814 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006815 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006816
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006817 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006818 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006819 return err_code;
6820
6821 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006822 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006823 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006824 err_code |= ERR_ALERT | ERR_FATAL;
6825 goto out;
6826}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006827
Christopher Faulet6f557912020-04-09 15:58:50 +02006828int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6829 const char *file, int line)
6830{
6831 int err_code = 0;
6832
Christopher Faulet6f557912020-04-09 15:58:50 +02006833 curpx->options2 &= ~PR_O2_CHK_ANY;
6834 curpx->options2 |= PR_O2_EXT_CHK;
6835 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6836 goto out;
6837
6838 out:
6839 return err_code;
6840}
6841
Christopher Fauletce8111e2020-04-06 15:04:11 +02006842/* Parse the "addr" server keyword */
6843static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6844 char **errmsg)
6845{
6846 struct sockaddr_storage *sk;
6847 struct protocol *proto;
6848 int port1, port2, err_code = 0;
6849
6850
6851 if (!*args[*cur_arg+1]) {
6852 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6853 goto error;
6854 }
6855
6856 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6857 if (!sk) {
6858 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6859 goto error;
6860 }
6861
6862 proto = protocol_by_family(sk->ss_family);
6863 if (!proto || !proto->connect) {
6864 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6865 args[*cur_arg], args[*cur_arg+1]);
6866 goto error;
6867 }
6868
6869 if (port1 != port2) {
6870 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6871 args[*cur_arg], args[*cur_arg+1]);
6872 goto error;
6873 }
6874
6875 srv->check.addr = srv->agent.addr = *sk;
6876 srv->flags |= SRV_F_CHECKADDR;
6877 srv->flags |= SRV_F_AGENTADDR;
6878
6879 out:
6880 return err_code;
6881
6882 error:
6883 err_code |= ERR_ALERT | ERR_FATAL;
6884 goto out;
6885}
6886
6887
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006888/* Parse the "agent-addr" server keyword */
6889static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6890 char **errmsg)
6891{
6892 int err_code = 0;
6893
6894 if (!*(args[*cur_arg+1])) {
6895 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6896 goto error;
6897 }
6898 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6899 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6900 goto error;
6901 }
6902
6903 out:
6904 return err_code;
6905
6906 error:
6907 err_code |= ERR_ALERT | ERR_FATAL;
6908 goto out;
6909}
6910
6911/* Parse the "agent-check" server keyword */
6912static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6913 char **errmsg)
6914{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006915 struct tcpcheck_ruleset *rs = NULL;
6916 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6917 struct tcpcheck_rule *chk;
6918 int err_code = 0;
6919
6920 if (srv->do_agent)
6921 goto out;
6922
6923 if (!rules) {
6924 rules = calloc(1, sizeof(*rules));
6925 if (!rules) {
6926 memprintf(errmsg, "out of memory.");
6927 goto error;
6928 }
6929 LIST_INIT(&rules->preset_vars);
6930 srv->agent.tcpcheck_rules = rules;
6931 }
6932 rules->list = NULL;
6933 rules->flags = 0;
6934
Christopher Faulet61cc8522020-04-20 14:54:42 +02006935 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006936 if (rs)
6937 goto ruleset_found;
6938
Christopher Faulet61cc8522020-04-20 14:54:42 +02006939 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006940 if (rs == NULL) {
6941 memprintf(errmsg, "out of memory.");
6942 goto error;
6943 }
6944
6945 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6946 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6947 if (!chk) {
6948 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6949 goto error;
6950 }
6951 chk->index = 0;
6952 LIST_ADDQ(&rs->rules, &chk->list);
6953
6954 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006955 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6956 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006957 if (!chk) {
6958 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6959 goto error;
6960 }
6961 chk->expect.custom = tcpcheck_agent_expect_reply;
6962 chk->index = 1;
6963 LIST_ADDQ(&rs->rules, &chk->list);
6964
Christopher Fauletd7cee712020-04-21 13:45:00 +02006965 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006966
6967 ruleset_found:
6968 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006969 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006970 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006971
6972 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006973 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006974
6975 error:
6976 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006977 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006978 err_code |= ERR_ALERT | ERR_FATAL;
6979 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006980}
6981
6982/* Parse the "agent-inter" server keyword */
6983static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6984 char **errmsg)
6985{
6986 const char *err = NULL;
6987 unsigned int delay;
6988 int err_code = 0;
6989
6990 if (!*(args[*cur_arg+1])) {
6991 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6992 goto error;
6993 }
6994
6995 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6996 if (err == PARSE_TIME_OVER) {
6997 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6998 args[*cur_arg+1], args[*cur_arg], srv->id);
6999 goto error;
7000 }
7001 else if (err == PARSE_TIME_UNDER) {
7002 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7003 args[*cur_arg+1], args[*cur_arg], srv->id);
7004 goto error;
7005 }
7006 else if (err) {
7007 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7008 *err, srv->id);
7009 goto error;
7010 }
7011 if (delay <= 0) {
7012 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7013 delay, args[*cur_arg], srv->id);
7014 goto error;
7015 }
7016 srv->agent.inter = delay;
7017
7018 out:
7019 return err_code;
7020
7021 error:
7022 err_code |= ERR_ALERT | ERR_FATAL;
7023 goto out;
7024}
7025
7026/* Parse the "agent-port" server keyword */
7027static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7028 char **errmsg)
7029{
7030 int err_code = 0;
7031
7032 if (!*(args[*cur_arg+1])) {
7033 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7034 goto error;
7035 }
7036
7037 global.maxsock++;
7038 srv->agent.port = atol(args[*cur_arg+1]);
7039
7040 out:
7041 return err_code;
7042
7043 error:
7044 err_code |= ERR_ALERT | ERR_FATAL;
7045 goto out;
7046}
7047
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007048int set_srv_agent_send(struct server *srv, const char *send)
7049{
7050 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7051 struct tcpcheck_var *var = NULL;
7052 char *str;
7053
7054 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007055 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007056 if (str == NULL || var == NULL)
7057 goto error;
7058
7059 free_tcpcheck_vars(&rules->preset_vars);
7060
7061 var->data.type = SMP_T_STR;
7062 var->data.u.str.area = str;
7063 var->data.u.str.data = strlen(str);
7064 LIST_INIT(&var->list);
7065 LIST_ADDQ(&rules->preset_vars, &var->list);
7066
7067 return 1;
7068
7069 error:
7070 free(str);
7071 free(var);
7072 return 0;
7073}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007074
7075/* Parse the "agent-send" server keyword */
7076static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7077 char **errmsg)
7078{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007079 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007080 int err_code = 0;
7081
7082 if (!*(args[*cur_arg+1])) {
7083 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7084 goto error;
7085 }
7086
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007087 if (!rules) {
7088 rules = calloc(1, sizeof(*rules));
7089 if (!rules) {
7090 memprintf(errmsg, "out of memory.");
7091 goto error;
7092 }
7093 LIST_INIT(&rules->preset_vars);
7094 srv->agent.tcpcheck_rules = rules;
7095 }
7096
7097 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007098 memprintf(errmsg, "out of memory.");
7099 goto error;
7100 }
7101
7102 out:
7103 return err_code;
7104
7105 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007106 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007107 err_code |= ERR_ALERT | ERR_FATAL;
7108 goto out;
7109}
7110
7111/* Parse the "no-agent-send" server keyword */
7112static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7113 char **errmsg)
7114{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007115 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007116 return 0;
7117}
7118
Christopher Fauletce8111e2020-04-06 15:04:11 +02007119/* Parse the "check" server keyword */
7120static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7121 char **errmsg)
7122{
7123 srv->do_check = 1;
7124 return 0;
7125}
7126
7127/* Parse the "check-send-proxy" server keyword */
7128static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7129 char **errmsg)
7130{
7131 srv->check.send_proxy = 1;
7132 return 0;
7133}
7134
7135/* Parse the "check-via-socks4" server keyword */
7136static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7137 char **errmsg)
7138{
7139 srv->check.via_socks4 = 1;
7140 return 0;
7141}
7142
7143/* Parse the "no-check" server keyword */
7144static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7145 char **errmsg)
7146{
7147 deinit_srv_check(srv);
7148 return 0;
7149}
7150
7151/* Parse the "no-check-send-proxy" server keyword */
7152static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7153 char **errmsg)
7154{
7155 srv->check.send_proxy = 0;
7156 return 0;
7157}
7158
7159/* Parse the "rise" server keyword */
7160static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7161 char **errmsg)
7162{
7163 int err_code = 0;
7164
7165 if (!*args[*cur_arg + 1]) {
7166 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7167 goto error;
7168 }
7169
7170 srv->check.rise = atol(args[*cur_arg+1]);
7171 if (srv->check.rise <= 0) {
7172 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7173 goto error;
7174 }
7175
7176 if (srv->check.health)
7177 srv->check.health = srv->check.rise;
7178
7179 out:
7180 return err_code;
7181
7182 error:
7183 deinit_srv_agent_check(srv);
7184 err_code |= ERR_ALERT | ERR_FATAL;
7185 goto out;
7186 return 0;
7187}
7188
7189/* Parse the "fall" server keyword */
7190static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7191 char **errmsg)
7192{
7193 int err_code = 0;
7194
7195 if (!*args[*cur_arg + 1]) {
7196 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7197 goto error;
7198 }
7199
7200 srv->check.fall = atol(args[*cur_arg+1]);
7201 if (srv->check.fall <= 0) {
7202 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7203 goto error;
7204 }
7205
7206 out:
7207 return err_code;
7208
7209 error:
7210 deinit_srv_agent_check(srv);
7211 err_code |= ERR_ALERT | ERR_FATAL;
7212 goto out;
7213 return 0;
7214}
7215
7216/* Parse the "inter" server keyword */
7217static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7218 char **errmsg)
7219{
7220 const char *err = NULL;
7221 unsigned int delay;
7222 int err_code = 0;
7223
7224 if (!*(args[*cur_arg+1])) {
7225 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7226 goto error;
7227 }
7228
7229 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7230 if (err == PARSE_TIME_OVER) {
7231 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7232 args[*cur_arg+1], args[*cur_arg], srv->id);
7233 goto error;
7234 }
7235 else if (err == PARSE_TIME_UNDER) {
7236 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7237 args[*cur_arg+1], args[*cur_arg], srv->id);
7238 goto error;
7239 }
7240 else if (err) {
7241 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7242 *err, srv->id);
7243 goto error;
7244 }
7245 if (delay <= 0) {
7246 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7247 delay, args[*cur_arg], srv->id);
7248 goto error;
7249 }
7250 srv->check.inter = delay;
7251
7252 out:
7253 return err_code;
7254
7255 error:
7256 err_code |= ERR_ALERT | ERR_FATAL;
7257 goto out;
7258}
7259
7260
7261/* Parse the "fastinter" server keyword */
7262static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7263 char **errmsg)
7264{
7265 const char *err = NULL;
7266 unsigned int delay;
7267 int err_code = 0;
7268
7269 if (!*(args[*cur_arg+1])) {
7270 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7271 goto error;
7272 }
7273
7274 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7275 if (err == PARSE_TIME_OVER) {
7276 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7277 args[*cur_arg+1], args[*cur_arg], srv->id);
7278 goto error;
7279 }
7280 else if (err == PARSE_TIME_UNDER) {
7281 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7282 args[*cur_arg+1], args[*cur_arg], srv->id);
7283 goto error;
7284 }
7285 else if (err) {
7286 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7287 *err, srv->id);
7288 goto error;
7289 }
7290 if (delay <= 0) {
7291 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7292 delay, args[*cur_arg], srv->id);
7293 goto error;
7294 }
7295 srv->check.fastinter = delay;
7296
7297 out:
7298 return err_code;
7299
7300 error:
7301 err_code |= ERR_ALERT | ERR_FATAL;
7302 goto out;
7303}
7304
7305
7306/* Parse the "downinter" server keyword */
7307static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7308 char **errmsg)
7309{
7310 const char *err = NULL;
7311 unsigned int delay;
7312 int err_code = 0;
7313
7314 if (!*(args[*cur_arg+1])) {
7315 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7316 goto error;
7317 }
7318
7319 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7320 if (err == PARSE_TIME_OVER) {
7321 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7322 args[*cur_arg+1], args[*cur_arg], srv->id);
7323 goto error;
7324 }
7325 else if (err == PARSE_TIME_UNDER) {
7326 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7327 args[*cur_arg+1], args[*cur_arg], srv->id);
7328 goto error;
7329 }
7330 else if (err) {
7331 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7332 *err, srv->id);
7333 goto error;
7334 }
7335 if (delay <= 0) {
7336 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7337 delay, args[*cur_arg], srv->id);
7338 goto error;
7339 }
7340 srv->check.downinter = delay;
7341
7342 out:
7343 return err_code;
7344
7345 error:
7346 err_code |= ERR_ALERT | ERR_FATAL;
7347 goto out;
7348}
7349
7350/* Parse the "port" server keyword */
7351static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7352 char **errmsg)
7353{
7354 int err_code = 0;
7355
7356 if (!*(args[*cur_arg+1])) {
7357 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7358 goto error;
7359 }
7360
7361 global.maxsock++;
7362 srv->check.port = atol(args[*cur_arg+1]);
7363 srv->flags |= SRV_F_CHECKPORT;
7364
7365 out:
7366 return err_code;
7367
7368 error:
7369 err_code |= ERR_ALERT | ERR_FATAL;
7370 goto out;
7371}
7372
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007373static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007374 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7375 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7376 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007377 { 0, NULL, NULL },
7378}};
7379
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007380static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007381 { "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 +02007382 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7383 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7384 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7385 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7386 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007387 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
7388 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7389 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007390 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007391 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7392 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7393 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7394 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7395 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7396 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7397 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7398 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007399 { NULL, NULL, 0 },
7400}};
7401
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007402INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007403INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007404
Willy Tarreaubd741542010-03-16 18:46:54 +01007405/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007406 * Local variables:
7407 * c-indent-level: 8
7408 * c-basic-offset: 8
7409 * End:
7410 */