blob: 0bd84c0e9e8d28d30e4738a06f33f3654840ae98 [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));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001063 break;
1064 case TCPCHK_EXPECT_CUSTOM:
1065 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1066 break;
1067 case TCPCHK_EXPECT_UNDEF:
1068 /* Should never happen. */
1069 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001070 }
1071
Christopher Faulet61cc8522020-04-20 14:54:42 +02001072 comment:
1073 /* If the failing expect rule provides a comment, it is concatenated to
1074 * the info message.
1075 */
1076 if (rule->comment) {
1077 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001078 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001079 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001080
Christopher Faulet61cc8522020-04-20 14:54:42 +02001081 /* Finally, the check status code is set if the failing expect rule
1082 * defines a status expression.
1083 */
1084 if (rule->expect.status_expr) {
1085 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1086 rule->expect.status_expr, SMP_T_SINT);
1087 if (smp)
1088 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001089 }
Simon Horman98637e52014-06-20 12:30:16 +09001090
Christopher Faulet61cc8522020-04-20 14:54:42 +02001091 *(b_tail(msg)) = '\0';
1092}
Cyril Bontéac92a062014-12-27 22:28:38 +01001093
Christopher Faulet61cc8522020-04-20 14:54:42 +02001094/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1095static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1096 struct ist info)
1097{
1098 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001099
Christopher Faulet61cc8522020-04-20 14:54:42 +02001100 /* Follows these step to produce the info message:
1101 * 1. if info field is already provided, copy it
1102 * 2. if the expect rule provides an onsucces log-format string,
1103 * use it to produce the message
1104 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1105 * 4. Otherwise produce the generic tcp-check info message
1106 */
1107 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001108 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001109 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1110 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1111 &rule->expect.onsuccess_fmt);
1112 else if (check->type == PR_O2_TCPCHK_CHK &&
1113 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1114 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001115
Christopher Faulet61cc8522020-04-20 14:54:42 +02001116 /* Finally, the check status code is set if the expect rule defines a
1117 * status expression.
1118 */
1119 if (rule->expect.status_expr) {
1120 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1121 rule->expect.status_expr, SMP_T_SINT);
1122 if (smp)
1123 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001124 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001125
1126 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001127}
1128
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129/* Builds the server state header used by HTTP health-checks */
1130static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001131{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001132 int sv_state;
1133 int ratio;
1134 char addr[46];
1135 char port[6];
1136 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1137 "UP %d/%d", "UP",
1138 "NOLB %d/%d", "NOLB",
1139 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001140
Christopher Faulet61cc8522020-04-20 14:54:42 +02001141 if (!(s->check.state & CHK_ST_ENABLED))
1142 sv_state = 6;
1143 else if (s->cur_state != SRV_ST_STOPPED) {
1144 if (s->check.health == s->check.rise + s->check.fall - 1)
1145 sv_state = 3; /* UP */
1146 else
1147 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001148
Christopher Faulet61cc8522020-04-20 14:54:42 +02001149 if (s->cur_state == SRV_ST_STOPPING)
1150 sv_state += 2;
1151 } else {
1152 if (s->check.health)
1153 sv_state = 1; /* going up */
1154 else
1155 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001156 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001157
Christopher Faulet61cc8522020-04-20 14:54:42 +02001158 chunk_appendf(buf, srv_hlt_st[sv_state],
1159 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1160 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 addr_to_str(&s->addr, addr, sizeof(addr));
1163 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1164 snprintf(port, sizeof(port), "%u", s->svc_port);
1165 else
1166 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001167
Christopher Faulet61cc8522020-04-20 14:54:42 +02001168 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1169 addr, port, s->proxy->id, s->id,
1170 global.node,
1171 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1172 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1173 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1174 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001175
Christopher Faulet61cc8522020-04-20 14:54:42 +02001176 if ((s->cur_state == SRV_ST_STARTING) &&
1177 now.tv_sec < s->last_change + s->slowstart &&
1178 now.tv_sec >= s->last_change) {
1179 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1180 chunk_appendf(buf, "; throttle=%d%%", ratio);
1181 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001182
Christopher Faulet61cc8522020-04-20 14:54:42 +02001183 return b_data(buf);
1184}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001185
Christopher Faulet61cc8522020-04-20 14:54:42 +02001186/* Internal functions to parse and validate a MySQL packet in the context of an
1187 * expect rule. It start to parse the input buffer at the offset <offset>. If
1188 * <last_read> is set, no more data are expected.
1189 */
1190static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1191 unsigned int offset, int last_read)
1192{
1193 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1194 enum healthcheck_status status;
1195 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001196 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001197 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001198
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001199
Christopher Faulet61cc8522020-04-20 14:54:42 +02001200 /* 3 Bytes for the packet length and 1 byte for the sequence id */
1201 if (!last_read && b_data(&check->bi) < offset+4) {
1202 if (!last_read)
1203 goto wait_more_data;
1204
1205 /* invalid length or truncated response */
1206 status = HCHK_STATUS_L7RSP;
1207 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001208 }
1209
Christopher Faulet61cc8522020-04-20 14:54:42 +02001210 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1211 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1212 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 if (b_data(&check->bi) < offset+plen+4) {
1215 if (!last_read)
1216 goto wait_more_data;
1217
1218 /* invalid length or truncated response */
1219 status = HCHK_STATUS_L7RSP;
1220 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001221 }
Simon Horman98637e52014-06-20 12:30:16 +09001222
Christopher Faulet61cc8522020-04-20 14:54:42 +02001223 if (*b_peek(&check->bi, offset+4) == '\xff') {
1224 /* MySQL Error packet always begin with field_count = 0xff */
1225 status = HCHK_STATUS_L7STS;
1226 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1227 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1228 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1229 goto error;
1230 }
Simon Horman98637e52014-06-20 12:30:16 +09001231
Christopher Faulet61cc8522020-04-20 14:54:42 +02001232 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1233 /* Not the last rule, continue */
1234 goto out;
1235 }
Simon Horman98637e52014-06-20 12:30:16 +09001236
Christopher Faulet61cc8522020-04-20 14:54:42 +02001237 /* We set the MySQL Version in description for information purpose
1238 * FIXME : it can be cool to use MySQL Version for other purpose,
1239 * like mark as down old MySQL server.
1240 */
1241 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001242
Christopher Faulet61cc8522020-04-20 14:54:42 +02001243 out:
1244 free_trash_chunk(msg);
1245 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 error:
1248 ret = TCPCHK_EVAL_STOP;
1249 check->code = err;
1250 msg = alloc_trash_chunk();
1251 if (msg)
1252 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1253 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1254 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001255
Christopher Faulet61cc8522020-04-20 14:54:42 +02001256 wait_more_data:
1257 ret = TCPCHK_EVAL_WAIT;
1258 goto out;
1259}
Simon Horman98637e52014-06-20 12:30:16 +09001260
Christopher Faulet61cc8522020-04-20 14:54:42 +02001261/* Custom tcp-check expect function to parse and validate the MySQL initial
1262 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1263 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1264 * error occurred.
1265 */
1266static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1267{
1268 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1269}
Simon Horman98637e52014-06-20 12:30:16 +09001270
Christopher Faulet61cc8522020-04-20 14:54:42 +02001271/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1272 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1273 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1274 * an error occurred.
1275 */
1276static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1277{
1278 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001279
Christopher Faulet61cc8522020-04-20 14:54:42 +02001280 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1281 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1282 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1285}
Simon Horman98637e52014-06-20 12:30:16 +09001286
Christopher Faulet61cc8522020-04-20 14:54:42 +02001287/* Custom tcp-check expect function to parse and validate the LDAP bind response
1288 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1289 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1290 * error occurred.
1291 */
1292static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1293{
1294 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1295 enum healthcheck_status status;
1296 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001297 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001298 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001299
Christopher Faulet61cc8522020-04-20 14:54:42 +02001300 /* Check if the server speaks LDAP (ASN.1/BER)
1301 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1302 * http://tools.ietf.org/html/rfc4511
1303 */
1304 /* size of LDAPMessage */
1305 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001306
Christopher Faulet61cc8522020-04-20 14:54:42 +02001307 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1308 * messageID: 0x02 0x01 0x01: INTEGER 1
1309 * protocolOp: 0x61: bindResponse
1310 */
1311 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1312 status = HCHK_STATUS_L7RSP;
1313 desc = ist("Not LDAPv3 protocol");
1314 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001315 }
Simon Horman98637e52014-06-20 12:30:16 +09001316
Christopher Faulet61cc8522020-04-20 14:54:42 +02001317 /* size of bindResponse */
1318 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001319
Christopher Faulet61cc8522020-04-20 14:54:42 +02001320 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1321 * ldapResult: 0x0a 0x01: ENUMERATION
1322 */
1323 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1324 status = HCHK_STATUS_L7RSP;
1325 desc = ist("Not LDAPv3 protocol");
1326 goto error;
1327 }
Simon Horman98637e52014-06-20 12:30:16 +09001328
Christopher Faulet61cc8522020-04-20 14:54:42 +02001329 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1330 * resultCode
1331 */
1332 check->code = *(b_head(&check->bi) + msglen + 9);
1333 if (check->code) {
1334 status = HCHK_STATUS_L7STS;
1335 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1336 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001337 }
1338
Christopher Faulet61cc8522020-04-20 14:54:42 +02001339 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001340
Christopher Faulet61cc8522020-04-20 14:54:42 +02001341 out:
1342 free_trash_chunk(msg);
1343 return ret;
1344
1345 error:
1346 ret = TCPCHK_EVAL_STOP;
1347 msg = alloc_trash_chunk();
1348 if (msg)
1349 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1350 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1351 goto out;
1352
1353 wait_more_data:
1354 ret = TCPCHK_EVAL_WAIT;
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
1659 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
1660 check->wait_list.events, &check->wait_list);
1661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
1686 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001687 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001688 *conn->dst = (is_addr(&connect->addr)
1689 ? connect->addr
1690 : (is_addr(&check->addr) ? check->addr : s->addr));
1691 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001692
Christopher Faulet61cc8522020-04-20 14:54:42 +02001693 port = 0;
1694 if (!port && connect->port)
1695 port = connect->port;
1696 if (!port && connect->port_expr) {
1697 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001698
Christopher Faulet61cc8522020-04-20 14:54:42 +02001699 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1700 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1701 connect->port_expr, SMP_T_SINT);
1702 if (smp)
1703 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001704 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001705 if (!port && is_inet_addr(&connect->addr))
1706 port = get_host_port(&connect->addr);
1707 if (!port && check->port)
1708 port = check->port;
1709 if (!port && is_inet_addr(&check->addr))
1710 port = get_host_port(&check->addr);
1711 if (!port)
1712 port = s->svc_port;
1713 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001714
Christopher Faulet61cc8522020-04-20 14:54:42 +02001715 xprt = ((connect->options & TCPCHK_OPT_SSL)
1716 ? xprt_get(XPRT_SSL)
1717 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 conn_prepare(conn, proto, xprt);
1720 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 status = SF_ERR_INTERNAL;
1723 if (proto && proto->connect) {
1724 struct tcpcheck_rule *next;
1725 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001726
Christopher Faulet61cc8522020-04-20 14:54:42 +02001727 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1728 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1731 if (!next || next->action != TCPCHK_ACT_EXPECT)
1732 flags |= CONNECT_DELACK_ALWAYS;
1733 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001734 }
1735
Christopher Faulet61cc8522020-04-20 14:54:42 +02001736 if (status != SF_ERR_NONE)
1737 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 conn->flags |= CO_FL_PRIVATE;
1740 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 /* The mux may be initialized now if there isn't server attached to the
1743 * check (email alerts) or if there is a mux proto specified or if there
1744 * is no alpn.
1745 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001746 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1747 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001748 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001749
Christopher Faulet61cc8522020-04-20 14:54:42 +02001750 if (connect->mux_proto)
1751 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001752 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 mux_ops = check->mux_proto->mux;
1754 else {
1755 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1756 ? PROTO_MODE_HTTP
1757 : PROTO_MODE_TCP);
1758
1759 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001760 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001761 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1762 status = SF_ERR_INTERNAL;
1763 goto fail_check;
1764 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001765 }
1766
Christopher Faulet61cc8522020-04-20 14:54:42 +02001767#ifdef USE_OPENSSL
1768 if (connect->sni)
1769 ssl_sock_set_servername(conn, connect->sni);
1770 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1771 ssl_sock_set_servername(conn, s->check.sni);
1772
1773 if (connect->alpn)
1774 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1775 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1776 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1777#endif
1778 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1779 conn->send_proxy_ofs = 1;
1780 conn->flags |= CO_FL_SOCKS4;
1781 }
1782 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1783 conn->send_proxy_ofs = 1;
1784 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001785 }
1786
Christopher Faulet61cc8522020-04-20 14:54:42 +02001787 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1788 conn->send_proxy_ofs = 1;
1789 conn->flags |= CO_FL_SEND_PROXY;
1790 }
1791 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1792 conn->send_proxy_ofs = 1;
1793 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001794 }
1795
Christopher Faulet61cc8522020-04-20 14:54:42 +02001796 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1797 /* Some servers don't like reset on close */
1798 fdtab[cs->conn->handle.fd].linger_risk = 0;
1799 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001800
Christopher Faulet61cc8522020-04-20 14:54:42 +02001801 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1802 if (xprt_add_hs(conn) < 0)
1803 status = SF_ERR_RESOURCE;
1804 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001805
Christopher Faulet61cc8522020-04-20 14:54:42 +02001806 fail_check:
1807 /* It can return one of :
1808 * - SF_ERR_NONE if everything's OK
1809 * - SF_ERR_SRVTO if there are no more servers
1810 * - SF_ERR_SRVCL if the connection was refused by the server
1811 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1812 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1813 * - SF_ERR_INTERNAL for any other purely internal errors
1814 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1815 * Note that we try to prevent the network stack from sending the ACK during the
1816 * connect() when a pure TCP check is used (without PROXY protocol).
1817 */
1818 switch (status) {
1819 case SF_ERR_NONE:
1820 /* we allow up to min(inter, timeout.connect) for a connection
1821 * to establish but only when timeout.check is set as it may be
1822 * to short for a full check otherwise
1823 */
1824 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001825
Christopher Faulet61cc8522020-04-20 14:54:42 +02001826 if (proxy->timeout.check && proxy->timeout.connect) {
1827 int t_con = tick_add(now_ms, proxy->timeout.connect);
1828 t->expire = tick_first(t->expire, t_con);
1829 }
1830 break;
1831 case SF_ERR_SRVTO: /* ETIMEDOUT */
1832 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1833 case SF_ERR_PRXCOND:
1834 case SF_ERR_RESOURCE:
1835 case SF_ERR_INTERNAL:
1836 chk_report_conn_err(check, errno, 0);
1837 ret = TCPCHK_EVAL_STOP;
1838 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001839 }
1840
Christopher Faulet61cc8522020-04-20 14:54:42 +02001841 /* don't do anything until the connection is established */
1842 if (conn->flags & CO_FL_WAIT_XPRT) {
1843 ret = TCPCHK_EVAL_WAIT;
1844 goto out;
1845 }
1846
1847 out:
1848 if (conn && check->result == CHK_RES_FAILED)
1849 conn->flags |= CO_FL_ERROR;
1850 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001851}
1852
Christopher Faulet61cc8522020-04-20 14:54:42 +02001853/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1854 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1855 * TCPCHK_EVAL_STOP if an error occurred.
1856 */
1857static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001858{
1859 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001860 struct tcpcheck_send *send = &rule->send;
1861 struct conn_stream *cs = check->cs;
1862 struct connection *conn = cs_conn(cs);
1863 struct buffer *tmp = NULL;
1864 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001865
Christopher Faulet61cc8522020-04-20 14:54:42 +02001866 /* reset the read & write buffer */
1867 b_reset(&check->bi);
1868 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001869
Christopher Faulet61cc8522020-04-20 14:54:42 +02001870 switch (send->type) {
1871 case TCPCHK_SEND_STRING:
1872 case TCPCHK_SEND_BINARY:
1873 if (istlen(send->data) >= b_size(&check->bo)) {
1874 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1875 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1876 tcpcheck_get_step_id(check, rule));
1877 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1878 ret = TCPCHK_EVAL_STOP;
1879 goto out;
1880 }
1881 b_putist(&check->bo, send->data);
1882 break;
1883 case TCPCHK_SEND_STRING_LF:
1884 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1885 if (!b_data(&check->bo))
1886 goto out;
1887 break;
1888 case TCPCHK_SEND_BINARY_LF:
1889 tmp = alloc_trash_chunk();
1890 if (!tmp)
1891 goto error_lf;
1892 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1893 if (!b_data(tmp))
1894 goto out;
1895 tmp->area[tmp->data] = '\0';
1896 b_set_data(&check->bo, b_size(&check->bo));
1897 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1898 goto error_lf;
1899 break;
1900 case TCPCHK_SEND_HTTP: {
1901 struct htx_sl *sl;
1902 struct ist meth, uri, vsn, clen, body;
1903 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001904
Christopher Faulet61cc8522020-04-20 14:54:42 +02001905 tmp = alloc_trash_chunk();
1906 if (!tmp)
1907 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001908
Christopher Faulet61cc8522020-04-20 14:54:42 +02001909 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1910 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1911 : http_known_methods[send->http.meth.meth]);
1912 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1913 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001914
Christopher Faulet61cc8522020-04-20 14:54:42 +02001915 if (istlen(vsn) == 8 &&
1916 (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))
1917 slflags |= HTX_SL_F_VER_11;
1918 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1919 if (!isttest(send->http.body))
1920 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001921
Christopher Faulet61cc8522020-04-20 14:54:42 +02001922 htx = htx_from_buf(&check->bo);
1923 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1924 if (!sl)
1925 goto error_htx;
1926 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001927
Christopher Faulet61cc8522020-04-20 14:54:42 +02001928 body = send->http.body; // TODO: handle body_fmt
1929 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001930
Christopher Faulet61cc8522020-04-20 14:54:42 +02001931 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1932 !htx_add_header(htx, ist("Content-length"), clen))
1933 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001934
Christopher Faulet61cc8522020-04-20 14:54:42 +02001935 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1936 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001937
Christopher Faulet61cc8522020-04-20 14:54:42 +02001938 list_for_each_entry(hdr, &send->http.hdrs, list) {
1939 chunk_reset(tmp);
1940 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1941 if (!b_data(tmp))
1942 continue;
1943 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1944 goto error_htx;
1945 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001946
Christopher Faulet61cc8522020-04-20 14:54:42 +02001947 }
1948 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1949 chunk_reset(tmp);
1950 httpchk_build_status_header(check->server, tmp);
1951 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1952 goto error_htx;
1953 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001954
Christopher Faulet61cc8522020-04-20 14:54:42 +02001955 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1956 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1957 !htx_add_endof(htx, HTX_BLK_EOM))
1958 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001959
Christopher Faulet61cc8522020-04-20 14:54:42 +02001960 htx_to_buf(htx, &check->bo);
1961 break;
1962 }
1963 case TCPCHK_SEND_UNDEF:
1964 /* Should never happen. */
1965 ret = TCPCHK_EVAL_STOP;
1966 goto out;
1967 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001968
Christopher Faulet6d471212020-04-22 11:09:25 +02001969
1970 if (conn->mux->snd_buf(cs, &check->bo,
1971 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001972 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001973 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001974 goto out;
1975 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001976 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001977 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001978 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1979 ret = TCPCHK_EVAL_WAIT;
1980 goto out;
1981 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001982
Christopher Faulet61cc8522020-04-20 14:54:42 +02001983 out:
1984 free_trash_chunk(tmp);
1985 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001986
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 error_htx:
1988 if (htx) {
1989 htx_reset(htx);
1990 htx_to_buf(htx, &check->bo);
1991 }
1992 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1993 tcpcheck_get_step_id(check, rule));
1994 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1995 ret = TCPCHK_EVAL_STOP;
1996 goto out;
1997
1998 error_lf:
1999 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2000 tcpcheck_get_step_id(check, rule));
2001 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2002 ret = TCPCHK_EVAL_STOP;
2003 goto out;
2004
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002005}
2006
Christopher Faulet61cc8522020-04-20 14:54:42 +02002007/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2008 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2009 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2010 * TCPCHK_EVAL_STOP if an error occurred.
2011 */
2012static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002013{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002014 struct conn_stream *cs = check->cs;
2015 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002016 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002017 size_t max, read, cur_read = 0;
2018 int is_empty;
2019 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002020
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021 if (check->wait_list.events & SUB_RETRY_RECV)
2022 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002023
Christopher Faulet61cc8522020-04-20 14:54:42 +02002024 if (cs->flags & CS_FL_EOS)
2025 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002026
Christopher Faulet61cc8522020-04-20 14:54:42 +02002027 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002028
Christopher Faulet61cc8522020-04-20 14:54:42 +02002029 /* prepare to detect if the mux needs more room */
2030 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002031
Christopher Faulet61cc8522020-04-20 14:54:42 +02002032 while ((cs->flags & CS_FL_RCV_MORE) ||
2033 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2034 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2035 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2036 cur_read += read;
2037 if (!read ||
2038 (cs->flags & CS_FL_WANT_ROOM) ||
2039 (--read_poll <= 0) ||
2040 (read < max && read >= global.tune.recv_enough))
2041 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002042 }
2043
Christopher Faulet61cc8522020-04-20 14:54:42 +02002044 end_recv:
2045 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2046 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2047 /* Report network errors only if we got no other data. Otherwise
2048 * we'll let the upper layers decide whether the response is OK
2049 * or not. It is very common that an RST sent by the server is
2050 * reported as an error just after the last data chunk.
2051 */
2052 goto stop;
2053 }
2054 if (!cur_read) {
2055 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2056 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2057 goto wait_more_data;
2058 }
2059 if (is_empty) {
2060 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2061 tcpcheck_get_step_id(check, rule));
2062 if (rule->comment)
2063 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2064 set_server_check_status(check, rule->expect.err_status, trash.area);
2065 goto stop;
2066 }
2067 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002068
2069 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002070 return ret;
2071
Christopher Faulet61cc8522020-04-20 14:54:42 +02002072 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002073 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002074 goto out;
2075
2076 wait_more_data:
2077 ret = TCPCHK_EVAL_WAIT;
2078 goto out;
2079}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002080
Christopher Faulet61cc8522020-04-20 14:54:42 +02002081/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2082 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2083 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2084 * error occurred.
2085 */
2086static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
Christopher Faulet267b01b2020-04-04 10:27:09 +02002087{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002088 struct htx *htx = htxbuf(&check->bi);
2089 struct htx_sl *sl;
2090 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002091 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002092 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002093 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002094 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002095 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002096 int match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002097
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002099
Christopher Faulet61cc8522020-04-20 14:54:42 +02002100 if (htx->flags & HTX_FL_PARSING_ERROR) {
2101 status = HCHK_STATUS_L7RSP;
2102 goto error;
2103 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002104
Christopher Faulet61cc8522020-04-20 14:54:42 +02002105 if (htx_is_empty(htx)) {
2106 if (last_read) {
2107 status = HCHK_STATUS_L7RSP;
2108 goto error;
2109 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002110 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002112
Christopher Faulet61cc8522020-04-20 14:54:42 +02002113 sl = http_get_stline(htx);
2114 check->code = sl->info.res.status;
2115
2116 if (check->server &&
2117 (check->server->proxy->options & PR_O_DISABLE404) &&
2118 (check->server->next_state != SRV_ST_STOPPED) &&
2119 (check->code == 404)) {
2120 /* 404 may be accepted as "stopping" only if the server was up */
2121 goto out;
2122 }
2123
2124 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2125 /* Make GCC happy ; initialize match to a failure state. */
2126 match = inverse;
2127
2128 switch (expect->type) {
2129 case TCPCHK_EXPECT_HTTP_STATUS:
2130 match = isteq(htx_sl_res_code(sl), expect->data);
2131
2132 /* Set status and description in case of error */
2133 status = HCHK_STATUS_L7STS;
2134 desc = htx_sl_res_reason(sl);
2135 break;
2136 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2137 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2138
2139 /* Set status and description in case of error */
2140 status = HCHK_STATUS_L7STS;
2141 desc = htx_sl_res_reason(sl);
2142 break;
2143
2144 case TCPCHK_EXPECT_HTTP_BODY:
2145 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2146 chunk_reset(&trash);
2147 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2148 enum htx_blk_type type = htx_get_blk_type(blk);
2149
2150 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2151 break;
2152 if (type == HTX_BLK_DATA) {
2153 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2154 break;
2155 }
2156 }
2157
2158 if (!b_data(&trash)) {
2159 if (!last_read)
2160 goto wait_more_data;
2161 status = HCHK_STATUS_L7RSP;
2162 desc = ist("HTTP content check could not find a response body");
2163 goto error;
2164 }
2165
2166 if (!last_read &&
2167 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2168 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2169 ret = TCPCHK_EVAL_WAIT;
2170 goto out;
2171 }
2172
2173 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002174 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002175 else
2176 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2177
2178 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002179 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002180 desc = (inverse
2181 ? ist("HTTP check matched unwanted content")
2182 : ist("HTTP content check did not match"));
2183 break;
2184
2185 default:
2186 /* should never happen */
2187 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002188 goto error;
2189 }
2190
Christopher Faulet61cc8522020-04-20 14:54:42 +02002191 /* Wait for more data on mismatch only if no minimum is defined (-1),
2192 * otherwise the absence of match is already conclusive.
2193 */
2194 if (!match && !last_read && (expect->min_recv == -1)) {
2195 ret = TCPCHK_EVAL_WAIT;
2196 goto out;
2197 }
2198
2199 if (!(match ^ inverse))
2200 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002201
2202 out:
2203 free_trash_chunk(msg);
2204 return ret;
2205
2206 error:
2207 ret = TCPCHK_EVAL_STOP;
2208 msg = alloc_trash_chunk();
2209 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002210 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002211 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2212 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002213
2214 wait_more_data:
2215 ret = TCPCHK_EVAL_WAIT;
2216 goto out;
2217}
2218
Christopher Faulet61cc8522020-04-20 14:54:42 +02002219/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2220 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2221 * if an error occurred.
2222 */
2223static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2224{
2225 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2226 struct tcpcheck_expect *expect = &rule->expect;
2227 struct buffer *msg = NULL;
2228 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002229
Christopher Faulet61cc8522020-04-20 14:54:42 +02002230 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002231
Christopher Faulet61cc8522020-04-20 14:54:42 +02002232 /* The current expect might need more data than the previous one, check again
2233 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002234 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002235 if (!last_read) {
2236 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2237 (b_data(&check->bi) < istlen(expect->data))) {
2238 ret = TCPCHK_EVAL_WAIT;
2239 goto out;
2240 }
2241 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2242 ret = TCPCHK_EVAL_WAIT;
2243 goto out;
2244 }
2245 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002246
Christopher Faulet61cc8522020-04-20 14:54:42 +02002247 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2248 /* Make GCC happy ; initialize match to a failure state. */
2249 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002250
Christopher Faulet61cc8522020-04-20 14:54:42 +02002251 switch (expect->type) {
2252 case TCPCHK_EXPECT_STRING:
2253 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002254 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 +02002255 break;
2256 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002257 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002258 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002259
Christopher Faulet61cc8522020-04-20 14:54:42 +02002260 case TCPCHK_EXPECT_REGEX_BINARY:
2261 chunk_reset(&trash);
2262 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002263 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002264 break;
2265 case TCPCHK_EXPECT_CUSTOM:
2266 if (expect->custom)
2267 ret = expect->custom(check, rule, last_read);
2268 goto out;
2269 default:
2270 /* Should never happen. */
2271 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002272 goto out;
2273 }
2274
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002275
Christopher Faulet61cc8522020-04-20 14:54:42 +02002276 /* Wait for more data on mismatch only if no minimum is defined (-1),
2277 * otherwise the absence of match is already conclusive.
2278 */
2279 if (!match && !last_read && (expect->min_recv == -1)) {
2280 ret = TCPCHK_EVAL_WAIT;
2281 goto out;
2282 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002283
Christopher Faulet61cc8522020-04-20 14:54:42 +02002284 /* Result as expected, next rule. */
2285 if (match ^ inverse)
2286 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002287
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002288
Christopher Faulet61cc8522020-04-20 14:54:42 +02002289 /* From this point on, we matched something we did not want, this is an error state. */
2290 ret = TCPCHK_EVAL_STOP;
2291 msg = alloc_trash_chunk();
2292 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002293 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002294 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2295 free_trash_chunk(msg);
2296 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002297
Christopher Faulet61cc8522020-04-20 14:54:42 +02002298 out:
2299 return ret;
2300}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002301
Christopher Faulet61cc8522020-04-20 14:54:42 +02002302/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2303 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2304 * waits.
2305 */
2306static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2307{
2308 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2309 struct act_rule *act_rule;
2310 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002311
Christopher Faulet61cc8522020-04-20 14:54:42 +02002312 act_rule =rule->action_kw.rule;
2313 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2314 if (act_ret != ACT_RET_CONT) {
2315 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2316 tcpcheck_get_step_id(check, rule));
2317 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2318 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002319 }
2320
Christopher Faulet61cc8522020-04-20 14:54:42 +02002321 return ret;
2322}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002323
Christopher Faulet61cc8522020-04-20 14:54:42 +02002324/* Executes a tcp-check ruleset. Note that this is called both from the
2325 * connection's wake() callback and from the check scheduling task. It returns
2326 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2327 * presenting the risk of an fd replacement.
2328 *
2329 * Please do NOT place any return statement in this function and only leave
2330 * via the out_end_tcpcheck label after setting retcode.
2331 */
2332static int tcpcheck_main(struct check *check)
2333{
2334 struct tcpcheck_rule *rule;
2335 struct conn_stream *cs = check->cs;
2336 struct connection *conn = cs_conn(cs);
2337 int must_read = 1, last_read = 0;
2338 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002339
Christopher Faulet61cc8522020-04-20 14:54:42 +02002340 /* here, we know that the check is complete or that it failed */
2341 if (check->result != CHK_RES_UNKNOWN)
2342 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002343
Christopher Faulet61cc8522020-04-20 14:54:42 +02002344 /* 1- check for connection error, if any */
2345 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2346 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002347
Christopher Faulet61cc8522020-04-20 14:54:42 +02002348 /* 2- check if we are waiting for the connection establishment. It only
2349 * happens during TCPCHK_ACT_CONNECT. */
2350 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2351 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2352 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2353 if (rule->action == TCPCHK_ACT_SEND)
2354 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2355 else if (rule->action == TCPCHK_ACT_EXPECT)
2356 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2357 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002358 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002359 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002360
2361 /* 3- check for pending outgoing data. It only happens during
2362 * TCPCHK_ACT_SEND. */
2363 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2364 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002365 ret = conn->mux->snd_buf(cs, &check->bo,
2366 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002367 if (ret <= 0) {
2368 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2369 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002370 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002371 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002372 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2373 goto out;
2374 }
2375 }
2376 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002377 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002378
Christopher Faulet61cc8522020-04-20 14:54:42 +02002379 /* 4- check if a rule must be resume. It happens if check->current_step
2380 * is defined. */
2381 else if (check->current_step)
2382 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002383
Christopher Faulet61cc8522020-04-20 14:54:42 +02002384 /* 5- It is the first evaluation. We must create a session and preset
2385 * tcp-check variables */
2386 else {
2387 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002388
Christopher Faulet61cc8522020-04-20 14:54:42 +02002389 /* First evaluation, create a session */
2390 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2391 if (!check->sess) {
2392 chunk_printf(&trash, "TCPCHK error allocating check session");
2393 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2394 goto out_end_tcpcheck;
2395 }
2396 vars_init(&check->vars, SCOPE_CHECK);
2397 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002398
Christopher Faulet61cc8522020-04-20 14:54:42 +02002399 /* Preset tcp-check variables */
2400 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2401 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002402
Christopher Faulet61cc8522020-04-20 14:54:42 +02002403 memset(&smp, 0, sizeof(smp));
2404 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2405 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002406 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002407 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002408 }
2409
Christopher Faulet61cc8522020-04-20 14:54:42 +02002410 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002411
Christopher Faulet61cc8522020-04-20 14:54:42 +02002412 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2413 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002414
Christopher Faulet61cc8522020-04-20 14:54:42 +02002415 check->code = 0;
2416 switch (rule->action) {
2417 case TCPCHK_ACT_CONNECT:
2418 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002419
Christopher Faulet61cc8522020-04-20 14:54:42 +02002420 /* close but not release yet previous connection */
2421 if (check->cs) {
2422 cs_close(check->cs);
2423 retcode = -1; /* do not reuse the fd in the caller! */
2424 }
2425 eval_ret = tcpcheck_eval_connect(check, rule);
2426 must_read = 1; last_read = 0;
2427 break;
2428 case TCPCHK_ACT_SEND:
2429 check->current_step = rule;
2430 eval_ret = tcpcheck_eval_send(check, rule);
2431 must_read = 1;
2432 break;
2433 case TCPCHK_ACT_EXPECT:
2434 check->current_step = rule;
2435 if (must_read) {
2436 if (check->proxy->timeout.check)
2437 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002438
Christopher Faulet61cc8522020-04-20 14:54:42 +02002439 eval_ret = tcpcheck_eval_recv(check, rule);
2440 if (eval_ret == TCPCHK_EVAL_STOP)
2441 goto out_end_tcpcheck;
2442 else if (eval_ret == TCPCHK_EVAL_WAIT)
2443 goto out;
2444 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2445 must_read = 0;
2446 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002447
Christopher Faulet61cc8522020-04-20 14:54:42 +02002448 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2449 ? tcpcheck_eval_expect_http(check, rule, last_read)
2450 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002451
Christopher Faulet61cc8522020-04-20 14:54:42 +02002452 if (eval_ret == TCPCHK_EVAL_WAIT) {
2453 check->current_step = rule->expect.head;
2454 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2455 }
2456 break;
2457 case TCPCHK_ACT_ACTION_KW:
2458 /* Don't update the current step */
2459 eval_ret = tcpcheck_eval_action_kw(check, rule);
2460 break;
2461 default:
2462 /* Otherwise, just go to the next one and don't update
2463 * the current step
2464 */
2465 eval_ret = TCPCHK_EVAL_CONTINUE;
2466 break;
2467 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002468
Christopher Faulet61cc8522020-04-20 14:54:42 +02002469 switch (eval_ret) {
2470 case TCPCHK_EVAL_CONTINUE:
2471 break;
2472 case TCPCHK_EVAL_WAIT:
2473 goto out;
2474 case TCPCHK_EVAL_STOP:
2475 goto out_end_tcpcheck;
2476 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002477 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002478
Christopher Faulet61cc8522020-04-20 14:54:42 +02002479 /* All rules was evaluated */
2480 if (check->current_step) {
2481 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002482
Christopher Faulet61cc8522020-04-20 14:54:42 +02002483 if (rule->action == TCPCHK_ACT_EXPECT) {
2484 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002485
Christopher Faulet61cc8522020-04-20 14:54:42 +02002486 if (check->server &&
2487 (check->server->proxy->options & PR_O_DISABLE404) &&
2488 (check->server->next_state != SRV_ST_STOPPED) &&
2489 (check->code == 404)) {
2490 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2491 goto out_end_tcpcheck;
2492 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002493
Christopher Faulet61cc8522020-04-20 14:54:42 +02002494 msg = alloc_trash_chunk();
2495 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002496 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002497 set_server_check_status(check, rule->expect.ok_status,
2498 (msg ? b_head(msg) : "(tcp-check)"));
2499 free_trash_chunk(msg);
2500 }
2501 else if (rule->action == TCPCHK_ACT_CONNECT) {
2502 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2503 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002504
Christopher Faulet61cc8522020-04-20 14:54:42 +02002505 set_server_check_status(check, status, msg);
2506 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002507 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002508 else
2509 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002510
Christopher Faulet61cc8522020-04-20 14:54:42 +02002511 out_end_tcpcheck:
2512 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2513 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002514
Christopher Faulet61cc8522020-04-20 14:54:42 +02002515 /* cleanup before leaving */
2516 check->current_step = NULL;
2517 if (check->sess != NULL) {
2518 vars_prune(&check->vars, check->sess, NULL);
2519 session_free(check->sess);
2520 check->sess = NULL;
2521 }
2522 out:
2523 return retcode;
2524}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002525
Christopher Faulet14cd3162020-04-16 14:50:06 +02002526
Christopher Faulet61cc8522020-04-20 14:54:42 +02002527/**************************************************************************/
2528/************** Health-checks based on an external process ****************/
2529/**************************************************************************/
2530static struct list pid_list = LIST_HEAD_INIT(pid_list);
2531static struct pool_head *pool_head_pid_list;
2532__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002533
Christopher Faulet61cc8522020-04-20 14:54:42 +02002534struct extcheck_env {
2535 char *name; /* environment variable name */
2536 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2537};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002538
Christopher Faulet61cc8522020-04-20 14:54:42 +02002539/* environment variables memory requirement for different types of data */
2540#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2541 * such environment variables are not updatable. */
2542#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2543#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2544#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002545
Christopher Faulet61cc8522020-04-20 14:54:42 +02002546/* external checks environment variables */
2547enum {
2548 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002549
Christopher Faulet61cc8522020-04-20 14:54:42 +02002550 /* Proxy specific environment variables */
2551 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2552 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2553 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2554 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002555
Christopher Faulet61cc8522020-04-20 14:54:42 +02002556 /* Server specific environment variables */
2557 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2558 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2559 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2560 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2561 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2562 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002563
Christopher Faulet61cc8522020-04-20 14:54:42 +02002564 EXTCHK_SIZE
2565};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002566
Christopher Faulet61cc8522020-04-20 14:54:42 +02002567const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2568 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2569 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2570 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2571 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2572 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2573 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2574 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2575 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2576 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2577 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2578 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2579};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002580
Christopher Faulet61cc8522020-04-20 14:54:42 +02002581void block_sigchld(void)
2582{
2583 sigset_t set;
2584 sigemptyset(&set);
2585 sigaddset(&set, SIGCHLD);
2586 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2587}
Willy Tarreaube373152018-09-06 11:45:30 +02002588
Christopher Faulet61cc8522020-04-20 14:54:42 +02002589void unblock_sigchld(void)
2590{
2591 sigset_t set;
2592 sigemptyset(&set);
2593 sigaddset(&set, SIGCHLD);
2594 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002595}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002596
Christopher Faulet61cc8522020-04-20 14:54:42 +02002597static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002598{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002599 struct pid_list *elem;
2600 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002601
Christopher Faulet61cc8522020-04-20 14:54:42 +02002602 elem = pool_alloc(pool_head_pid_list);
2603 if (!elem)
2604 return NULL;
2605 elem->pid = pid;
2606 elem->t = t;
2607 elem->exited = 0;
2608 check->curpid = elem;
2609 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002610
Christopher Faulet61cc8522020-04-20 14:54:42 +02002611 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2612 LIST_ADD(&pid_list, &elem->list);
2613 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002614
Christopher Faulet61cc8522020-04-20 14:54:42 +02002615 return elem;
2616}
Christopher Faulete5870d82020-04-15 11:32:03 +02002617
Christopher Faulet61cc8522020-04-20 14:54:42 +02002618static void pid_list_del(struct pid_list *elem)
2619{
2620 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002621
Christopher Faulet61cc8522020-04-20 14:54:42 +02002622 if (!elem)
2623 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2626 LIST_DEL(&elem->list);
2627 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002628
Christopher Faulet61cc8522020-04-20 14:54:42 +02002629 if (!elem->exited)
2630 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632 check = elem->t->context;
2633 check->curpid = NULL;
2634 pool_free(pool_head_pid_list, elem);
2635}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002636
Christopher Faulet61cc8522020-04-20 14:54:42 +02002637/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2638static void pid_list_expire(pid_t pid, int status)
2639{
2640 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002641
Christopher Faulet61cc8522020-04-20 14:54:42 +02002642 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2643 list_for_each_entry(elem, &pid_list, list) {
2644 if (elem->pid == pid) {
2645 elem->t->expire = now_ms;
2646 elem->status = status;
2647 elem->exited = 1;
2648 task_wakeup(elem->t, TASK_WOKEN_IO);
2649 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002650 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002651 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002652 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2653}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002654
Christopher Faulet61cc8522020-04-20 14:54:42 +02002655static void sigchld_handler(struct sig_handler *sh)
2656{
2657 pid_t pid;
2658 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002659
Christopher Faulet61cc8522020-04-20 14:54:42 +02002660 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2661 pid_list_expire(pid, status);
2662}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002663
Christopher Faulet61cc8522020-04-20 14:54:42 +02002664static int init_pid_list(void)
2665{
2666 if (pool_head_pid_list != NULL)
2667 /* Nothing to do */
2668 return 0;
2669
2670 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2671 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2672 strerror(errno));
2673 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002674 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002675
Christopher Faulet61cc8522020-04-20 14:54:42 +02002676 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2677 if (pool_head_pid_list == NULL) {
2678 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2679 strerror(errno));
2680 return 1;
2681 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002682
Christopher Faulet61cc8522020-04-20 14:54:42 +02002683 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002684}
2685
Christopher Faulet61cc8522020-04-20 14:54:42 +02002686/* helper macro to set an environment variable and jump to a specific label on failure. */
2687#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002688
Christopher Faulet61cc8522020-04-20 14:54:42 +02002689/*
2690 * helper function to allocate enough memory to store an environment variable.
2691 * It will also check that the environment variable is updatable, and silently
2692 * fail if not.
2693 */
2694static int extchk_setenv(struct check *check, int idx, const char *value)
2695{
2696 int len, ret;
2697 char *envname;
2698 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002699
Christopher Faulet61cc8522020-04-20 14:54:42 +02002700 if (idx < 0 || idx >= EXTCHK_SIZE) {
2701 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2702 return 1;
2703 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002704
Christopher Faulet61cc8522020-04-20 14:54:42 +02002705 envname = extcheck_envs[idx].name;
2706 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002707
Christopher Faulet61cc8522020-04-20 14:54:42 +02002708 /* Check if the environment variable is already set, and silently reject
2709 * the update if this one is not updatable. */
2710 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2711 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002712
Christopher Faulet61cc8522020-04-20 14:54:42 +02002713 /* Instead of sending NOT_USED, sending an empty value is preferable */
2714 if (strcmp(value, "NOT_USED") == 0) {
2715 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002716 }
2717
Christopher Faulet61cc8522020-04-20 14:54:42 +02002718 len = strlen(envname) + 1;
2719 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2720 len += strlen(value);
2721 else
2722 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002723
Christopher Faulet61cc8522020-04-20 14:54:42 +02002724 if (!check->envp[idx])
2725 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002726
Christopher Faulet61cc8522020-04-20 14:54:42 +02002727 if (!check->envp[idx]) {
2728 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2729 return 1;
2730 }
2731 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2732 if (ret < 0) {
2733 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2734 return 1;
2735 }
2736 else if (ret > len) {
2737 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2738 return 1;
2739 }
2740 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002741}
2742
Christopher Faulet61cc8522020-04-20 14:54:42 +02002743static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002744{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002745 struct server *s = check->server;
2746 struct proxy *px = s->proxy;
2747 struct listener *listener = NULL, *l;
2748 int i;
2749 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2750 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002751
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752 list_for_each_entry(l, &px->conf.listeners, by_fe)
2753 /* Use the first INET, INET6 or UNIX listener */
2754 if (l->addr.ss_family == AF_INET ||
2755 l->addr.ss_family == AF_INET6 ||
2756 l->addr.ss_family == AF_UNIX) {
2757 listener = l;
2758 break;
2759 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002760
Christopher Faulet61cc8522020-04-20 14:54:42 +02002761 check->curpid = NULL;
2762 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2763 if (!check->envp) {
2764 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2765 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002766 }
2767
Christopher Faulet61cc8522020-04-20 14:54:42 +02002768 check->argv = calloc(6, sizeof(char *));
2769 if (!check->argv) {
2770 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2771 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002772 }
2773
Christopher Faulet61cc8522020-04-20 14:54:42 +02002774 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002775
Christopher Faulet61cc8522020-04-20 14:54:42 +02002776 if (!listener) {
2777 check->argv[1] = strdup("NOT_USED");
2778 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002779 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002780 else if (listener->addr.ss_family == AF_INET ||
2781 listener->addr.ss_family == AF_INET6) {
2782 addr_to_str(&listener->addr, buf, sizeof(buf));
2783 check->argv[1] = strdup(buf);
2784 port_to_str(&listener->addr, buf, sizeof(buf));
2785 check->argv[2] = strdup(buf);
2786 }
2787 else if (listener->addr.ss_family == AF_UNIX) {
2788 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002789
Christopher Faulet61cc8522020-04-20 14:54:42 +02002790 un = (struct sockaddr_un *)&listener->addr;
2791 check->argv[1] = strdup(un->sun_path);
2792 check->argv[2] = strdup("NOT_USED");
2793 }
2794 else {
2795 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2796 goto err;
2797 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002798
Christopher Faulet61cc8522020-04-20 14:54:42 +02002799 if (!check->argv[1] || !check->argv[2]) {
2800 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2801 goto err;
2802 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002803
Christopher Faulet61cc8522020-04-20 14:54:42 +02002804 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2805 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2806 if (!check->argv[3] || !check->argv[4]) {
2807 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2808 goto err;
2809 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002810
Christopher Faulet61cc8522020-04-20 14:54:42 +02002811 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2812 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2813 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002814
Christopher Faulet61cc8522020-04-20 14:54:42 +02002815 for (i = 0; i < 5; i++) {
2816 if (!check->argv[i]) {
2817 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2818 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002819 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002820 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002821
Christopher Faulet61cc8522020-04-20 14:54:42 +02002822 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2823 /* Add proxy environment variables */
2824 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2825 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2826 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2827 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2828 /* Add server environment variables */
2829 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2830 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2831 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2832 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2833 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2834 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002835
Christopher Faulet61cc8522020-04-20 14:54:42 +02002836 /* Ensure that we don't leave any hole in check->envp */
2837 for (i = 0; i < EXTCHK_SIZE; i++)
2838 if (!check->envp[i])
2839 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002840
Christopher Faulet61cc8522020-04-20 14:54:42 +02002841 return 1;
2842err:
2843 if (check->envp) {
2844 for (i = 0; i < EXTCHK_SIZE; i++)
2845 free(check->envp[i]);
2846 free(check->envp);
2847 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002848 }
2849
Christopher Faulet61cc8522020-04-20 14:54:42 +02002850 if (check->argv) {
2851 for (i = 1; i < 5; i++)
2852 free(check->argv[i]);
2853 free(check->argv);
2854 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002855 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002856 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002857}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002858
Christopher Faulet61cc8522020-04-20 14:54:42 +02002859/*
2860 * establish a server health-check that makes use of a process.
2861 *
2862 * It can return one of :
2863 * - SF_ERR_NONE if everything's OK
2864 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2865 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2866 *
2867 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002868 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002869static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002870{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002871 char buf[256];
2872 struct check *check = t->context;
2873 struct server *s = check->server;
2874 struct proxy *px = s->proxy;
2875 int status;
2876 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002877
Christopher Faulet61cc8522020-04-20 14:54:42 +02002878 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002879
Christopher Faulet61cc8522020-04-20 14:54:42 +02002880 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002881
Christopher Faulet61cc8522020-04-20 14:54:42 +02002882 pid = fork();
2883 if (pid < 0) {
2884 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2885 (global.tune.options & GTUNE_INSECURE_FORK) ?
2886 "" : " (likely caused by missing 'insecure-fork-wanted')",
2887 strerror(errno));
2888 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002889 goto out;
2890 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002891 if (pid == 0) {
2892 /* Child */
2893 extern char **environ;
2894 struct rlimit limit;
2895 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002896
Christopher Faulet61cc8522020-04-20 14:54:42 +02002897 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2898 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002899
Christopher Faulet61cc8522020-04-20 14:54:42 +02002900 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002901
Christopher Faulet61cc8522020-04-20 14:54:42 +02002902 /* restore the initial FD limits */
2903 limit.rlim_cur = rlim_fd_cur_at_boot;
2904 limit.rlim_max = rlim_fd_max_at_boot;
2905 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2906 getrlimit(RLIMIT_NOFILE, &limit);
2907 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2908 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2909 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2910 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002911
Christopher Faulet61cc8522020-04-20 14:54:42 +02002912 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002913
Christopher Faulet61cc8522020-04-20 14:54:42 +02002914 /* Update some environment variables and command args: curconn, server addr and server port */
2915 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002916
Christopher Faulet61cc8522020-04-20 14:54:42 +02002917 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2918 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002919
Christopher Faulet61cc8522020-04-20 14:54:42 +02002920 *check->argv[4] = 0;
2921 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2922 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2923 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002924
Christopher Faulet61cc8522020-04-20 14:54:42 +02002925 haproxy_unblock_signals();
2926 execvp(px->check_command, check->argv);
2927 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2928 strerror(errno));
2929 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002930 }
2931
Christopher Faulet61cc8522020-04-20 14:54:42 +02002932 /* Parent */
2933 if (check->result == CHK_RES_UNKNOWN) {
2934 if (pid_list_add(pid, t) != NULL) {
2935 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2936
2937 if (px->timeout.check && px->timeout.connect) {
2938 int t_con = tick_add(now_ms, px->timeout.connect);
2939 t->expire = tick_first(t->expire, t_con);
2940 }
2941 status = SF_ERR_NONE;
2942 goto out;
2943 }
2944 else {
2945 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2946 }
2947 kill(pid, SIGTERM); /* process creation error */
2948 }
2949 else
2950 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2951
2952out:
2953 unblock_sigchld();
2954 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002955}
2956
Christopher Faulet61cc8522020-04-20 14:54:42 +02002957/*
2958 * manages a server health-check that uses an external process. Returns
2959 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002960 *
2961 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002962 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002963 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002964static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002965{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002966 struct check *check = context;
2967 struct server *s = check->server;
2968 int rv;
2969 int ret;
2970 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002971
Christopher Faulet61cc8522020-04-20 14:54:42 +02002972 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2973 if (!(check->state & CHK_ST_INPROGRESS)) {
2974 /* no check currently running */
2975 if (!expired) /* woke up too early */
2976 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002977
Christopher Faulet61cc8522020-04-20 14:54:42 +02002978 /* we don't send any health-checks when the proxy is
2979 * stopped, the server should not be checked or the check
2980 * is disabled.
2981 */
2982 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2983 s->proxy->state == PR_STSTOPPED)
2984 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002985
Christopher Faulet61cc8522020-04-20 14:54:42 +02002986 /* we'll initiate a new check */
2987 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002988
Christopher Faulet61cc8522020-04-20 14:54:42 +02002989 check->state |= CHK_ST_INPROGRESS;
2990
2991 ret = connect_proc_chk(t);
2992 if (ret == SF_ERR_NONE) {
2993 /* the process was forked, we allow up to min(inter,
2994 * timeout.connect) for it to report its status, but
2995 * only when timeout.check is set as it may be to short
2996 * for a full check otherwise.
2997 */
2998 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2999
3000 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3001 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3002 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003003 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003004 task_set_affinity(t, tid_bit);
3005 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003006 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003007
Christopher Faulet61cc8522020-04-20 14:54:42 +02003008 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003009
Christopher Faulet61cc8522020-04-20 14:54:42 +02003010 check->state &= ~CHK_ST_INPROGRESS;
3011 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003012
Christopher Faulet61cc8522020-04-20 14:54:42 +02003013 /* we allow up to min(inter, timeout.connect) for a connection
3014 * to establish but only when timeout.check is set
3015 * as it may be to short for a full check otherwise
3016 */
3017 while (tick_is_expired(t->expire, now_ms)) {
3018 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003019
Christopher Faulet61cc8522020-04-20 14:54:42 +02003020 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3021 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003022
Christopher Faulet61cc8522020-04-20 14:54:42 +02003023 if (s->proxy->timeout.check)
3024 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003025 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003026 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003027 else {
3028 /* there was a test running.
3029 * First, let's check whether there was an uncaught error,
3030 * which can happen on connect timeout or error.
3031 */
3032 if (check->result == CHK_RES_UNKNOWN) {
3033 /* good connection is enough for pure TCP check */
3034 struct pid_list *elem = check->curpid;
3035 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003036
Christopher Faulet61cc8522020-04-20 14:54:42 +02003037 if (elem->exited) {
3038 status = elem->status; /* Save in case the process exits between use below */
3039 if (!WIFEXITED(status))
3040 check->code = -1;
3041 else
3042 check->code = WEXITSTATUS(status);
3043 if (!WIFEXITED(status) || WEXITSTATUS(status))
3044 status = HCHK_STATUS_PROCERR;
3045 else
3046 status = HCHK_STATUS_PROCOK;
3047 } else if (expired) {
3048 status = HCHK_STATUS_PROCTOUT;
3049 ha_warning("kill %d\n", (int)elem->pid);
3050 kill(elem->pid, SIGTERM);
3051 }
3052 set_server_check_status(check, status, NULL);
3053 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003054
Christopher Faulet61cc8522020-04-20 14:54:42 +02003055 if (check->result == CHK_RES_FAILED) {
3056 /* a failure or timeout detected */
3057 check_notify_failure(check);
3058 }
3059 else if (check->result == CHK_RES_CONDPASS) {
3060 /* check is OK but asks for stopping mode */
3061 check_notify_stopping(check);
3062 }
3063 else if (check->result == CHK_RES_PASSED) {
3064 /* a success was detected */
3065 check_notify_success(check);
3066 }
3067 task_set_affinity(t, 1);
3068 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003069
Christopher Faulet61cc8522020-04-20 14:54:42 +02003070 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003071
Christopher Faulet61cc8522020-04-20 14:54:42 +02003072 rv = 0;
3073 if (global.spread_checks > 0) {
3074 rv = srv_getinter(check) * global.spread_checks / 100;
3075 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3076 }
3077 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3078 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003079
Christopher Faulet61cc8522020-04-20 14:54:42 +02003080 reschedule:
3081 while (tick_is_expired(t->expire, now_ms))
3082 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003083
Christopher Faulet61cc8522020-04-20 14:54:42 +02003084 out_unlock:
3085 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3086 return t;
3087}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003088
Baptiste Assmann248f1172018-03-01 21:49:01 +01003089
Christopher Faulet61cc8522020-04-20 14:54:42 +02003090/**************************************************************************/
3091/***************** Health-checks based on connections *********************/
3092/**************************************************************************/
3093/* This function is used only for server health-checks. It handles connection
3094 * status updates including errors. If necessary, it wakes the check task up.
3095 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3096 * connection (eg: reconnect). It relies on tcpcheck_main().
3097 */
3098static int wake_srv_chk(struct conn_stream *cs)
3099{
3100 struct connection *conn = cs->conn;
3101 struct check *check = cs->data;
3102 struct email_alertq *q = container_of(check, typeof(*q), check);
3103 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003104
Christopher Faulet61cc8522020-04-20 14:54:42 +02003105 if (check->server)
3106 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3107 else
3108 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003109
Christopher Faulet61cc8522020-04-20 14:54:42 +02003110 /* we may have to make progress on the TCP checks */
3111 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003112
Christopher Faulet61cc8522020-04-20 14:54:42 +02003113 cs = check->cs;
3114 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003115
Christopher Faulet61cc8522020-04-20 14:54:42 +02003116 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3117 /* We may get error reports bypassing the I/O handlers, typically
3118 * the case when sending a pure TCP check which fails, then the I/O
3119 * handlers above are not called. This is completely handled by the
3120 * main processing task so let's simply wake it up. If we get here,
3121 * we expect errno to still be valid.
3122 */
3123 chk_report_conn_err(check, errno, 0);
3124 task_wakeup(check->task, TASK_WOKEN_IO);
3125 }
3126
3127 if (check->result != CHK_RES_UNKNOWN) {
3128 /* Check complete or aborted. If connection not yet closed do it
3129 * now and wake the check task up to be sure the result is
3130 * handled ASAP. */
3131 conn_sock_drain(conn);
3132 cs_close(cs);
3133 ret = -1;
3134 /* We may have been scheduled to run, and the
3135 * I/O handler expects to have a cs, so remove
3136 * the tasklet
3137 */
3138 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3139 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003140 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003141
3142 if (check->server)
3143 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003144 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003145 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003146
Christopher Faulet61cc8522020-04-20 14:54:42 +02003147 /* if a connection got replaced, we must absolutely prevent the connection
3148 * handler from touching its fd, and perform the FD polling updates ourselves
3149 */
3150 if (ret < 0)
3151 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003152
Christopher Faulet61cc8522020-04-20 14:54:42 +02003153 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003154}
3155
Christopher Faulet61cc8522020-04-20 14:54:42 +02003156/* This function checks if any I/O is wanted, and if so, attempts to do so */
3157static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003158{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003159 struct check *check = ctx;
3160 struct conn_stream *cs = check->cs;
3161 struct email_alertq *q = container_of(check, typeof(*q), check);
3162 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003163
Christopher Faulet61cc8522020-04-20 14:54:42 +02003164 if (!(check->wait_list.events & SUB_RETRY_SEND))
3165 ret = wake_srv_chk(cs);
3166 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3167 if (check->server)
3168 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3169 else
3170 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003171
Christopher Faulet61cc8522020-04-20 14:54:42 +02003172 if (unlikely(check->result == CHK_RES_FAILED)) {
3173 /* collect possible new errors */
3174 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3175 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003176
Christopher Faulet61cc8522020-04-20 14:54:42 +02003177 /* Reset the check buffer... */
3178 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003179
Christopher Faulet61cc8522020-04-20 14:54:42 +02003180 /* Close the connection... We still attempt to nicely close if,
3181 * for instance, SSL needs to send a "close notify." Later, we perform
3182 * a hard close and reset the connection if some data are pending,
3183 * otherwise we end up with many TIME_WAITs and eat all the source port
3184 * range quickly. To avoid sending RSTs all the time, we first try to
3185 * drain pending data.
3186 */
3187 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3188 * connection, to make sure cs_shutw() will not lead to a shutdown()
3189 * that would provoke TIME_WAITs.
3190 */
3191 cs_shutr(cs, CS_SHR_DRAIN);
3192 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003193
Christopher Faulet61cc8522020-04-20 14:54:42 +02003194 /* OK, let's not stay here forever */
3195 if (check->result == CHK_RES_FAILED)
3196 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003197
Christopher Faulet61cc8522020-04-20 14:54:42 +02003198 task_wakeup(t, TASK_WOKEN_IO);
3199 }
3200
3201 if (check->server)
3202 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3203 else
3204 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003205 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003206 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003207}
3208
Christopher Faulet61cc8522020-04-20 14:54:42 +02003209/* manages a server health-check that uses a connection. Returns
3210 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3211 *
3212 * Please do NOT place any return statement in this function and only leave
3213 * via the out_unlock label.
3214 */
3215static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003216{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003217 struct check *check = context;
3218 struct proxy *proxy = check->proxy;
3219 struct conn_stream *cs = check->cs;
3220 struct connection *conn = cs_conn(cs);
3221 int rv;
3222 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003223
Christopher Faulet61cc8522020-04-20 14:54:42 +02003224 if (check->server)
3225 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3226 if (!(check->state & CHK_ST_INPROGRESS)) {
3227 /* no check currently running */
3228 if (!expired) /* woke up too early */
3229 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003230
Christopher Faulet61cc8522020-04-20 14:54:42 +02003231 /* we don't send any health-checks when the proxy is
3232 * stopped, the server should not be checked or the check
3233 * is disabled.
3234 */
3235 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3236 proxy->state == PR_STSTOPPED)
3237 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003238
Christopher Faulet61cc8522020-04-20 14:54:42 +02003239 /* we'll initiate a new check */
3240 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003241
Christopher Faulet61cc8522020-04-20 14:54:42 +02003242 check->state |= CHK_ST_INPROGRESS;
3243 b_reset(&check->bi);
3244 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003245
Christopher Faulet61cc8522020-04-20 14:54:42 +02003246 task_set_affinity(t, tid_bit);
3247 cs = check->cs;
3248 conn = cs_conn(cs);
3249 if (!conn) {
3250 check->current_step = NULL;
3251 tcpcheck_main(check);
3252 goto out_unlock;
3253 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003254
Christopher Faulet61cc8522020-04-20 14:54:42 +02003255 conn->flags |= CO_FL_ERROR;
3256 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003257
Christopher Faulet61cc8522020-04-20 14:54:42 +02003258 /* here, we have seen a synchronous error, no fd was allocated */
3259 task_set_affinity(t, MAX_THREADS_MASK);
3260 if (cs) {
3261 if (check->wait_list.events)
3262 cs->conn->xprt->unsubscribe(cs->conn,
3263 cs->conn->xprt_ctx,
3264 check->wait_list.events,
3265 &check->wait_list);
3266 /* We may have been scheduled to run, and the
3267 * I/O handler expects to have a cs, so remove
3268 * the tasklet
3269 */
3270 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3271 cs_destroy(cs);
3272 cs = check->cs = NULL;
3273 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003274 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003275
Christopher Faulet61cc8522020-04-20 14:54:42 +02003276 check->state &= ~CHK_ST_INPROGRESS;
3277 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003278
Christopher Faulet61cc8522020-04-20 14:54:42 +02003279 /* we allow up to min(inter, timeout.connect) for a connection
3280 * to establish but only when timeout.check is set
3281 * as it may be to short for a full check otherwise
3282 */
3283 while (tick_is_expired(t->expire, now_ms)) {
3284 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003285
Christopher Faulet61cc8522020-04-20 14:54:42 +02003286 t_con = tick_add(t->expire, proxy->timeout.connect);
3287 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3288 if (proxy->timeout.check)
3289 t->expire = tick_first(t->expire, t_con);
3290 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003291 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003292 else {
3293 /* there was a test running.
3294 * First, let's check whether there was an uncaught error,
3295 * which can happen on connect timeout or error.
3296 */
3297 if (check->result == CHK_RES_UNKNOWN) {
3298 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3299 chk_report_conn_err(check, 0, expired);
3300 }
3301 else
3302 goto out_unlock; /* timeout not reached, wait again */
3303 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003304
Christopher Faulet61cc8522020-04-20 14:54:42 +02003305 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003306
Christopher Faulet61cc8522020-04-20 14:54:42 +02003307 check->current_step = NULL;
3308 if (check->sess != NULL) {
3309 session_free(check->sess);
3310 check->sess = NULL;
3311 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003312
Christopher Faulet61cc8522020-04-20 14:54:42 +02003313 if (conn && conn->xprt) {
3314 /* The check was aborted and the connection was not yet closed.
3315 * This can happen upon timeout, or when an external event such
3316 * as a failed response coupled with "observe layer7" caused the
3317 * server state to be suddenly changed.
3318 */
3319 conn_sock_drain(conn);
3320 cs_close(cs);
3321 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003322
Christopher Faulet61cc8522020-04-20 14:54:42 +02003323 if (cs) {
3324 if (check->wait_list.events)
3325 cs->conn->xprt->unsubscribe(cs->conn,
3326 cs->conn->xprt_ctx,
3327 check->wait_list.events,
3328 &check->wait_list);
3329 /* We may have been scheduled to run, and the
3330 * I/O handler expects to have a cs, so remove
3331 * the tasklet
3332 */
3333 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3334 cs_destroy(cs);
3335 cs = check->cs = NULL;
3336 conn = NULL;
3337 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003338
Christopher Faulet61cc8522020-04-20 14:54:42 +02003339 if (check->server) {
3340 if (check->result == CHK_RES_FAILED) {
3341 /* a failure or timeout detected */
3342 check_notify_failure(check);
3343 }
3344 else if (check->result == CHK_RES_CONDPASS) {
3345 /* check is OK but asks for stopping mode */
3346 check_notify_stopping(check);
3347 }
3348 else if (check->result == CHK_RES_PASSED) {
3349 /* a success was detected */
3350 check_notify_success(check);
3351 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003352 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003353 task_set_affinity(t, MAX_THREADS_MASK);
3354 check->state &= ~CHK_ST_INPROGRESS;
3355
3356 if (check->server) {
3357 rv = 0;
3358 if (global.spread_checks > 0) {
3359 rv = srv_getinter(check) * global.spread_checks / 100;
3360 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3361 }
3362 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003363 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003364 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003365
Christopher Faulet61cc8522020-04-20 14:54:42 +02003366 reschedule:
3367 while (tick_is_expired(t->expire, now_ms))
3368 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3369 out_unlock:
3370 if (check->server)
3371 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3372 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003373}
3374
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003375
Christopher Faulet61cc8522020-04-20 14:54:42 +02003376/**************************************************************************/
3377/******************* Internals to parse tcp-check rules *******************/
3378/**************************************************************************/
3379struct action_kw_list tcp_check_keywords = {
3380 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3381};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003382
Christopher Faulet61cc8522020-04-20 14:54:42 +02003383/* Return the struct action_kw associated to a keyword */
3384static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003385{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003386 return action_lookup(&tcp_check_keywords.list, kw);
3387}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003388
Christopher Faulet61cc8522020-04-20 14:54:42 +02003389static void action_kw_tcp_check_build_list(struct buffer *chk)
3390{
3391 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003392}
3393
Christopher Faulet61cc8522020-04-20 14:54:42 +02003394/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3395 * returned on error.
3396 */
3397static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3398 struct list *rules, struct action_kw *kw,
3399 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003400{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003401 struct tcpcheck_rule *chk = NULL;
3402 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003403
Christopher Faulet61cc8522020-04-20 14:54:42 +02003404 actrule = calloc(1, sizeof(*actrule));
3405 if (!actrule) {
3406 memprintf(errmsg, "out of memory");
3407 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003408 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003409 actrule->kw = kw;
3410 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003411
Christopher Faulet61cc8522020-04-20 14:54:42 +02003412 cur_arg++;
3413 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3414 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3415 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003416 }
3417
Christopher Faulet61cc8522020-04-20 14:54:42 +02003418 chk = calloc(1, sizeof(*chk));
3419 if (!chk) {
3420 memprintf(errmsg, "out of memory");
3421 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003422 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003423 chk->action = TCPCHK_ACT_ACTION_KW;
3424 chk->action_kw.rule = actrule;
3425 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003426
3427 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003428 free(actrule);
3429 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003430}
3431
Christopher Faulet61cc8522020-04-20 14:54:42 +02003432/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3433 * returned on error.
3434 */
3435static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3436 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003437{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003438 struct tcpcheck_rule *chk = NULL;
3439 struct sockaddr_storage *sk = NULL;
3440 char *comment = NULL, *sni = NULL, *alpn = NULL;
3441 struct sample_expr *port_expr = NULL;
3442 unsigned short conn_opts = 0;
3443 long port = 0;
3444 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003445
Christopher Faulet61cc8522020-04-20 14:54:42 +02003446 list_for_each_entry(chk, rules, list) {
3447 if (chk->action == TCPCHK_ACT_CONNECT)
3448 break;
3449 if (chk->action == TCPCHK_ACT_COMMENT ||
3450 chk->action == TCPCHK_ACT_ACTION_KW ||
3451 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3452 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003453
Christopher Faulet61cc8522020-04-20 14:54:42 +02003454 memprintf(errmsg, "first step MUST also be a 'connect', "
3455 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3456 "when there is a 'connect' step in the tcp-check ruleset");
3457 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003458 }
3459
Christopher Faulet61cc8522020-04-20 14:54:42 +02003460 cur_arg++;
3461 while (*(args[cur_arg])) {
3462 if (strcmp(args[cur_arg], "default") == 0)
3463 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3464 else if (strcmp(args[cur_arg], "addr") == 0) {
3465 int port1, port2;
3466 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003467
Christopher Faulet61cc8522020-04-20 14:54:42 +02003468 if (!*(args[cur_arg+1])) {
3469 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3470 goto error;
3471 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003472
Christopher Faulet61cc8522020-04-20 14:54:42 +02003473 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3474 if (!sk) {
3475 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3476 goto error;
3477 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003478
Christopher Faulet61cc8522020-04-20 14:54:42 +02003479 proto = protocol_by_family(sk->ss_family);
3480 if (!proto || !proto->connect) {
3481 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3482 args[cur_arg]);
3483 goto error;
3484 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003485
Christopher Faulet61cc8522020-04-20 14:54:42 +02003486 if (port1 != port2) {
3487 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3488 args[cur_arg], args[cur_arg+1]);
3489 goto error;
3490 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003491
Christopher Faulet61cc8522020-04-20 14:54:42 +02003492 cur_arg++;
3493 }
3494 else if (strcmp(args[cur_arg], "port") == 0) {
3495 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003496
Christopher Faulet61cc8522020-04-20 14:54:42 +02003497 if (!*(args[cur_arg+1])) {
3498 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3499 goto error;
3500 }
3501 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003502
Christopher Faulet61cc8522020-04-20 14:54:42 +02003503 port = 0;
3504 release_sample_expr(port_expr);
3505 p = args[cur_arg]; end = p + strlen(p);
3506 port = read_uint(&p, end);
3507 if (p != end) {
3508 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003509
Christopher Faulet61cc8522020-04-20 14:54:42 +02003510 px->conf.args.ctx = ARGC_SRV;
3511 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3512 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003513
Christopher Faulet61cc8522020-04-20 14:54:42 +02003514 if (!port_expr) {
3515 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3516 goto error;
3517 }
3518 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3519 memprintf(errmsg, "error detected while parsing port expression : "
3520 " fetch method '%s' extracts information from '%s', "
3521 "none of which is available here.\n",
3522 args[cur_arg], sample_src_names(port_expr->fetch->use));
3523 goto error;
3524 }
3525 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3526 }
3527 else if (port > 65535 || port < 1) {
3528 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3529 args[cur_arg]);
3530 goto error;
3531 }
3532 }
3533 else if (strcmp(args[cur_arg], "comment") == 0) {
3534 if (!*(args[cur_arg+1])) {
3535 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3536 goto error;
3537 }
3538 cur_arg++;
3539 free(comment);
3540 comment = strdup(args[cur_arg]);
3541 if (!comment) {
3542 memprintf(errmsg, "out of memory");
3543 goto error;
3544 }
3545 }
3546 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3547 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3548 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3549 conn_opts |= TCPCHK_OPT_SOCKS4;
3550 else if (strcmp(args[cur_arg], "linger") == 0)
3551 conn_opts |= TCPCHK_OPT_LINGER;
3552#ifdef USE_OPENSSL
3553 else if (strcmp(args[cur_arg], "ssl") == 0) {
3554 px->options |= PR_O_TCPCHK_SSL;
3555 conn_opts |= TCPCHK_OPT_SSL;
3556 }
3557 else if (strcmp(args[cur_arg], "sni") == 0) {
3558 if (!*(args[cur_arg+1])) {
3559 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3560 goto error;
3561 }
3562 cur_arg++;
3563 free(sni);
3564 sni = strdup(args[cur_arg]);
3565 if (!sni) {
3566 memprintf(errmsg, "out of memory");
3567 goto error;
3568 }
3569 }
3570 else if (strcmp(args[cur_arg], "alpn") == 0) {
3571#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3572 free(alpn);
3573 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3574 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3575 goto error;
3576 }
3577 cur_arg++;
3578#else
3579 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003580 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003581#endif
3582 }
3583#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003584
Christopher Faulet61cc8522020-04-20 14:54:42 +02003585 else {
3586 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3587#ifdef USE_OPENSSL
3588 ", 'ssl', 'sni', 'alpn'"
3589#endif /* USE_OPENSSL */
3590 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3591 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003592 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003593 }
3594 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003595 }
3596
Christopher Faulet61cc8522020-04-20 14:54:42 +02003597 chk = calloc(1, sizeof(*chk));
3598 if (!chk) {
3599 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003600 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003601 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003602 chk->action = TCPCHK_ACT_CONNECT;
3603 chk->comment = comment;
3604 chk->connect.port = port;
3605 chk->connect.options = conn_opts;
3606 chk->connect.sni = sni;
3607 chk->connect.alpn = alpn;
3608 chk->connect.alpn_len= alpn_len;
3609 chk->connect.port_expr= port_expr;
3610 if (sk)
3611 chk->connect.addr = *sk;
3612 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003613
Christopher Faulet61cc8522020-04-20 14:54:42 +02003614 error:
3615 free(alpn);
3616 free(sni);
3617 free(comment);
3618 release_sample_expr(port_expr);
3619 return NULL;
3620}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621
Christopher Faulet61cc8522020-04-20 14:54:42 +02003622/* Parses and creates a tcp-check send rule. NULL is returned on error */
3623static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3624 const char *file, int line, char **errmsg)
3625{
3626 struct tcpcheck_rule *chk = NULL;
3627 char *comment = NULL, *data = NULL;
3628 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003629
Christopher Faulet61cc8522020-04-20 14:54:42 +02003630 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3631 if (!*(args[cur_arg+1])) {
3632 memprintf(errmsg, "'%s' expects a %s as argument",
3633 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003634 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003635 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003636
Christopher Faulet61cc8522020-04-20 14:54:42 +02003637 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003638
Christopher Faulet61cc8522020-04-20 14:54:42 +02003639 cur_arg += 2;
3640 while (*(args[cur_arg])) {
3641 if (strcmp(args[cur_arg], "comment") == 0) {
3642 if (!*(args[cur_arg+1])) {
3643 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3644 goto error;
3645 }
3646 cur_arg++;
3647 free(comment);
3648 comment = strdup(args[cur_arg]);
3649 if (!comment) {
3650 memprintf(errmsg, "out of memory");
3651 goto error;
3652 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003653 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003654 else if (strcmp(args[cur_arg], "log-format") == 0) {
3655 if (type == TCPCHK_SEND_BINARY)
3656 type = TCPCHK_SEND_BINARY_LF;
3657 else if (type == TCPCHK_SEND_STRING)
3658 type = TCPCHK_SEND_STRING_LF;
3659 }
3660 else {
3661 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3662 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003663 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003664 }
3665 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003666 }
3667
Christopher Faulet61cc8522020-04-20 14:54:42 +02003668 chk = calloc(1, sizeof(*chk));
3669 if (!chk) {
3670 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003671 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003672 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003673 chk->action = TCPCHK_ACT_SEND;
3674 chk->comment = comment;
3675 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003676
Christopher Faulet61cc8522020-04-20 14:54:42 +02003677 switch (chk->send.type) {
3678 case TCPCHK_SEND_STRING:
3679 chk->send.data = ist2(strdup(data), strlen(data));
3680 if (!isttest(chk->send.data)) {
3681 memprintf(errmsg, "out of memory");
3682 goto error;
3683 }
3684 break;
3685 case TCPCHK_SEND_BINARY:
3686 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3687 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3688 goto error;
3689 }
3690 break;
3691 case TCPCHK_SEND_STRING_LF:
3692 case TCPCHK_SEND_BINARY_LF:
3693 LIST_INIT(&chk->send.fmt);
3694 px->conf.args.ctx = ARGC_SRV;
3695 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3696 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3697 goto error;
3698 }
3699 break;
3700 case TCPCHK_SEND_HTTP:
3701 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003702 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003703 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003704
Christopher Faulet61cc8522020-04-20 14:54:42 +02003705 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003706
Christopher Faulet61cc8522020-04-20 14:54:42 +02003707 error:
3708 free(chk);
3709 free(comment);
3710 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003711}
3712
Christopher Faulet61cc8522020-04-20 14:54:42 +02003713/* Parses and creates a http-check send rule. NULL is returned on error */
3714static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3715 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003716{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003717 struct tcpcheck_rule *chk = NULL;
3718 struct tcpcheck_http_hdr *hdr = NULL;
3719 struct http_hdr hdrs[global.tune.max_http_hdr];
3720 char *meth = NULL, *uri = NULL, *vsn = NULL;
3721 char *body = NULL, *comment = NULL;
3722 unsigned int flags = 0;
3723 int i = 0;
3724
3725 cur_arg++;
3726 while (*(args[cur_arg])) {
3727 if (strcmp(args[cur_arg], "meth") == 0) {
3728 if (!*(args[cur_arg+1])) {
3729 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3730 goto error;
3731 }
3732 cur_arg++;
3733 meth = args[cur_arg];
3734 }
3735 else if (strcmp(args[cur_arg], "uri") == 0) {
3736 if (!*(args[cur_arg+1])) {
3737 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3738 goto error;
3739 }
3740 cur_arg++;
3741 uri = args[cur_arg];
3742 // TODO: log-format uri
3743 }
3744 else if (strcmp(args[cur_arg], "vsn") == 0) {
3745 if (!*(args[cur_arg+1])) {
3746 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3747 goto error;
3748 }
3749 cur_arg++;
3750 vsn = args[cur_arg];
3751 }
3752 else if (strcmp(args[cur_arg], "hdr") == 0) {
3753 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3754 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3755 goto error;
3756 }
3757 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3758 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3759 i++;
3760 cur_arg += 2;
3761 }
3762 else if (strcmp(args[cur_arg], "body") == 0) {
3763 if (!*(args[cur_arg+1])) {
3764 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3765 goto error;
3766 }
3767 cur_arg++;
3768 body = args[cur_arg];
3769 // TODO: log-format body
3770 }
3771 else if (strcmp(args[cur_arg], "comment") == 0) {
3772 if (!*(args[cur_arg+1])) {
3773 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3774 goto error;
3775 }
3776 cur_arg++;
3777 free(comment);
3778 comment = strdup(args[cur_arg]);
3779 if (!comment) {
3780 memprintf(errmsg, "out of memory");
3781 goto error;
3782 }
3783 }
3784 else {
3785 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
3786 args[cur_arg]);
3787 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003788 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003789 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003790 }
3791
Christopher Faulet61cc8522020-04-20 14:54:42 +02003792 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003793
Christopher Faulet61cc8522020-04-20 14:54:42 +02003794 chk = calloc(1, sizeof(*chk));
3795 if (!chk) {
3796 memprintf(errmsg, "out of memory");
3797 goto error;
3798 }
3799 chk->action = TCPCHK_ACT_SEND;
3800 chk->comment = comment; comment = NULL;
3801 chk->send.type = TCPCHK_SEND_HTTP;
3802 chk->send.http.flags = flags;
3803 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003804
Christopher Faulet61cc8522020-04-20 14:54:42 +02003805 if (meth) {
3806 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3807 chk->send.http.meth.str.area = strdup(meth);
3808 chk->send.http.meth.str.data = strlen(meth);
3809 if (!chk->send.http.meth.str.area) {
3810 memprintf(errmsg, "out of memory");
3811 goto error;
3812 }
3813 }
3814 if (uri) {
3815 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3816 if (!isttest(chk->send.http.uri)) {
3817 memprintf(errmsg, "out of memory");
3818 goto error;
3819 }
3820 }
3821 if (vsn) {
3822 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3823 if (!isttest(chk->send.http.vsn)) {
3824 memprintf(errmsg, "out of memory");
3825 goto error;
3826 }
3827 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003828 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003829 hdr = calloc(1, sizeof(*hdr));
3830 if (!hdr) {
3831 memprintf(errmsg, "out of memory");
3832 goto error;
3833 }
3834 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003835 hdr->name = istdup(hdrs[i].n);
3836 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003837 memprintf(errmsg, "out of memory");
3838 goto error;
3839 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003840
Christopher Fauletb61caf42020-04-21 10:57:42 +02003841 ist0(hdrs[i].v);
3842 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 +02003843 goto error;
3844 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3845 hdr = NULL;
3846 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003847
Christopher Faulet61cc8522020-04-20 14:54:42 +02003848 if (body) {
3849 chk->send.http.body = ist2(strdup(body), strlen(body));
3850 if (!isttest(chk->send.http.body)) {
3851 memprintf(errmsg, "out of memory");
3852 goto error;
3853 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003854 }
3855
Christopher Faulet61cc8522020-04-20 14:54:42 +02003856 return chk;
3857
3858 error:
3859 free_tcpcheck_http_hdr(hdr);
3860 free_tcpcheck(chk, 0);
3861 free(comment);
3862 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003863}
3864
Christopher Faulet61cc8522020-04-20 14:54:42 +02003865/* Parses and creates a http-check comment rule. NULL is returned on error */
3866static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3867 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003868{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003869 struct tcpcheck_rule *chk = NULL;
3870 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003871
Christopher Faulet61cc8522020-04-20 14:54:42 +02003872 if (!*(args[cur_arg+1])) {
3873 memprintf(errmsg, "expects a string as argument");
3874 goto error;
3875 }
3876 cur_arg++;
3877 comment = strdup(args[cur_arg]);
3878 if (!comment) {
3879 memprintf(errmsg, "out of memory");
3880 goto error;
3881 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003882
Christopher Faulet61cc8522020-04-20 14:54:42 +02003883 chk = calloc(1, sizeof(*chk));
3884 if (!chk) {
3885 memprintf(errmsg, "out of memory");
3886 goto error;
3887 }
3888 chk->action = TCPCHK_ACT_COMMENT;
3889 chk->comment = comment;
3890 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003891
Christopher Faulet61cc8522020-04-20 14:54:42 +02003892 error:
3893 free(comment);
3894 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003895}
3896
Christopher Faulet61cc8522020-04-20 14:54:42 +02003897/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3898 * on error. <proto> is set to the right protocol flags (covered by the
3899 * TCPCHK_RULES_PROTO_CHK mask).
3900 */
3901static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3902 struct list *rules, unsigned int proto,
3903 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003904{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003905 struct tcpcheck_rule *prev_check, *chk = NULL;
3906 struct sample_expr *status_expr = NULL;
3907 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
3908 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3909 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3910 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3911 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3912 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003913 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003914
Christopher Faulet61cc8522020-04-20 14:54:42 +02003915 str = on_success_msg = on_error_msg = comment = pattern = NULL;
3916 if (!*(args[cur_arg+1])) {
3917 memprintf(errmsg, "expects at least a matching pattern as arguments");
3918 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003919 }
3920
Christopher Faulet61cc8522020-04-20 14:54:42 +02003921 cur_arg++;
3922 while (*(args[cur_arg])) {
3923 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003924
Christopher Faulet61cc8522020-04-20 14:54:42 +02003925 rescan:
3926 if (strcmp(args[cur_arg], "min-recv") == 0) {
3927 if (in_pattern) {
3928 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3929 goto error;
3930 }
3931 if (!*(args[cur_arg+1])) {
3932 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3933 goto error;
3934 }
3935 /* Use an signed integer here because of chksize */
3936 cur_arg++;
3937 min_recv = atol(args[cur_arg]);
3938 if (min_recv < -1 || min_recv > INT_MAX) {
3939 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3940 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003941 }
3942 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003943 else if (*(args[cur_arg]) == '!') {
3944 in_pattern = 1;
3945 while (*(args[cur_arg]) == '!') {
3946 inverse = !inverse;
3947 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003948 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003949 if (!*(args[cur_arg]))
3950 cur_arg++;
3951 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003952 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003953 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3954 if (type != TCPCHK_EXPECT_UNDEF) {
3955 memprintf(errmsg, "only on pattern expected");
3956 goto error;
3957 }
3958 if (proto != TCPCHK_RULES_HTTP_CHK)
3959 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3960 else
3961 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003962
Christopher Faulet61cc8522020-04-20 14:54:42 +02003963 if (!*(args[cur_arg+1])) {
3964 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3965 goto error;
3966 }
3967 cur_arg++;
3968 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003969 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003970 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3971 if (proto == TCPCHK_RULES_HTTP_CHK)
3972 goto bad_http_kw;
3973 if (type != TCPCHK_EXPECT_UNDEF) {
3974 memprintf(errmsg, "only on pattern expected");
3975 goto error;
3976 }
3977 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003978
Christopher Faulet61cc8522020-04-20 14:54:42 +02003979 if (!*(args[cur_arg+1])) {
3980 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3981 goto error;
3982 }
3983 cur_arg++;
3984 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003985 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003986 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3987 if (proto != TCPCHK_RULES_HTTP_CHK)
3988 goto bad_tcp_kw;
3989 if (type != TCPCHK_EXPECT_UNDEF) {
3990 memprintf(errmsg, "only on pattern expected");
3991 goto error;
3992 }
3993 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003994
Christopher Faulet61cc8522020-04-20 14:54:42 +02003995 if (!*(args[cur_arg+1])) {
3996 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3997 goto error;
3998 }
3999 cur_arg++;
4000 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004001 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004002 else if (strcmp(args[cur_arg], "custom") == 0) {
4003 if (in_pattern) {
4004 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4005 goto error;
4006 }
4007 if (type != TCPCHK_EXPECT_UNDEF) {
4008 memprintf(errmsg, "only on pattern expected");
4009 goto error;
4010 }
4011 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004012 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004013 else if (strcmp(args[cur_arg], "comment") == 0) {
4014 if (in_pattern) {
4015 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4016 goto error;
4017 }
4018 if (!*(args[cur_arg+1])) {
4019 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4020 goto error;
4021 }
4022 cur_arg++;
4023 free(comment);
4024 comment = strdup(args[cur_arg]);
4025 if (!comment) {
4026 memprintf(errmsg, "out of memory");
4027 goto error;
4028 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004029 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004030 else if (strcmp(args[cur_arg], "on-success") == 0) {
4031 if (in_pattern) {
4032 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4033 goto error;
4034 }
4035 if (!*(args[cur_arg+1])) {
4036 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4037 goto error;
4038 }
4039 cur_arg++;
4040 free(on_success_msg);
4041 on_success_msg = strdup(args[cur_arg]);
4042 if (!on_success_msg) {
4043 memprintf(errmsg, "out of memory");
4044 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004045 }
4046 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004047 else if (strcmp(args[cur_arg], "on-error") == 0) {
4048 if (in_pattern) {
4049 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4050 goto error;
4051 }
4052 if (!*(args[cur_arg+1])) {
4053 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4054 goto error;
4055 }
4056 cur_arg++;
4057 free(on_error_msg);
4058 on_error_msg = strdup(args[cur_arg]);
4059 if (!on_error_msg) {
4060 memprintf(errmsg, "out of memory");
4061 goto error;
4062 }
4063 }
4064 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4065 if (in_pattern) {
4066 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4067 goto error;
4068 }
4069 if (!*(args[cur_arg+1])) {
4070 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4071 goto error;
4072 }
4073 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4074 ok_st = HCHK_STATUS_L7OKD;
4075 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4076 ok_st = HCHK_STATUS_L7OKCD;
4077 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4078 ok_st = HCHK_STATUS_L6OK;
4079 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4080 ok_st = HCHK_STATUS_L4OK;
4081 else {
4082 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4083 args[cur_arg], args[cur_arg+1]);
4084 goto error;
4085 }
4086 cur_arg++;
4087 }
4088 else if (strcmp(args[cur_arg], "error-status") == 0) {
4089 if (in_pattern) {
4090 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4091 goto error;
4092 }
4093 if (!*(args[cur_arg+1])) {
4094 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4095 goto error;
4096 }
4097 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4098 err_st = HCHK_STATUS_L7RSP;
4099 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4100 err_st = HCHK_STATUS_L7STS;
4101 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4102 err_st = HCHK_STATUS_L6RSP;
4103 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4104 err_st = HCHK_STATUS_L4CON;
4105 else {
4106 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4107 args[cur_arg], args[cur_arg+1]);
4108 goto error;
4109 }
4110 cur_arg++;
4111 }
4112 else if (strcmp(args[cur_arg], "status-code") == 0) {
4113 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004114
Christopher Faulet61cc8522020-04-20 14:54:42 +02004115 if (in_pattern) {
4116 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4117 goto error;
4118 }
4119 if (!*(args[cur_arg+1])) {
4120 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4121 goto error;
4122 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004123
Christopher Faulet61cc8522020-04-20 14:54:42 +02004124 cur_arg++;
4125 release_sample_expr(status_expr);
4126 px->conf.args.ctx = ARGC_SRV;
4127 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4128 file, line, errmsg, &px->conf.args, NULL);
4129 if (!status_expr) {
4130 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4131 goto error;
4132 }
4133 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4134 memprintf(errmsg, "error detected while parsing status-code expression : "
4135 " fetch method '%s' extracts information from '%s', "
4136 "none of which is available here.\n",
4137 args[cur_arg], sample_src_names(status_expr->fetch->use));
4138 goto error;
4139 }
4140 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4141 }
4142 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4143 if (in_pattern) {
4144 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4145 goto error;
4146 }
4147 if (!*(args[cur_arg+1])) {
4148 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4149 goto error;
4150 }
4151 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4152 tout_st = HCHK_STATUS_L7TOUT;
4153 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4154 tout_st = HCHK_STATUS_L6TOUT;
4155 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4156 tout_st = HCHK_STATUS_L4TOUT;
4157 else {
4158 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4159 args[cur_arg], args[cur_arg+1]);
4160 goto error;
4161 }
4162 cur_arg++;
4163 }
4164 else {
4165 if (proto == TCPCHK_RULES_HTTP_CHK) {
4166 bad_http_kw:
4167 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4168 " or comment but got '%s' as argument.", args[cur_arg]);
4169 }
4170 else {
4171 bad_tcp_kw:
4172 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4173 " or comment but got '%s' as argument.", args[cur_arg]);
4174 }
4175 goto error;
4176 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004177
Christopher Faulet61cc8522020-04-20 14:54:42 +02004178 cur_arg++;
4179 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004180
Christopher Faulet61cc8522020-04-20 14:54:42 +02004181 chk = calloc(1, sizeof(*chk));
4182 if (!chk) {
4183 memprintf(errmsg, "out of memory");
4184 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004185 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004186 chk->action = TCPCHK_ACT_EXPECT;
4187 LIST_INIT(&chk->expect.onerror_fmt);
4188 LIST_INIT(&chk->expect.onsuccess_fmt);
4189 chk->comment = comment; comment = NULL;
4190 chk->expect.type = type;
4191 chk->expect.min_recv = min_recv;
4192 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004193 chk->expect.ok_status = ok_st;
4194 chk->expect.err_status = err_st;
4195 chk->expect.tout_status = tout_st;
4196 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004197
Christopher Faulet61cc8522020-04-20 14:54:42 +02004198 if (on_success_msg) {
4199 px->conf.args.ctx = ARGC_SRV;
4200 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4201 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4202 goto error;
4203 }
4204 free(on_success_msg);
4205 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004206 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004207 if (on_error_msg) {
4208 px->conf.args.ctx = ARGC_SRV;
4209 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4210 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4211 goto error;
4212 }
4213 free(on_error_msg);
4214 on_error_msg = NULL;
4215 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004216
Christopher Faulet61cc8522020-04-20 14:54:42 +02004217 switch (chk->expect.type) {
4218 case TCPCHK_EXPECT_STRING:
4219 case TCPCHK_EXPECT_HTTP_STATUS:
4220 case TCPCHK_EXPECT_HTTP_BODY:
4221 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004222 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004223 memprintf(errmsg, "out of memory");
4224 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004225 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004226 break;
4227 case TCPCHK_EXPECT_BINARY:
4228 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4229 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4230 goto error;
4231 }
4232 case TCPCHK_EXPECT_REGEX:
4233 case TCPCHK_EXPECT_REGEX_BINARY:
4234 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4235 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004236 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004237 if (!chk->expect.regex)
4238 goto error;
4239 break;
4240 case TCPCHK_EXPECT_CUSTOM:
4241 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4242 break;
4243 case TCPCHK_EXPECT_UNDEF:
4244 free(chk);
4245 memprintf(errmsg, "pattern not found");
4246 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004247 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004248
Christopher Faulet61cc8522020-04-20 14:54:42 +02004249 /* All tcp-check expect points back to the first inverse expect rule in
4250 * a chain of one or more expect rule, potentially itself.
4251 */
4252 chk->expect.head = chk;
4253 list_for_each_entry_rev(prev_check, rules, list) {
4254 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4255 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4256 chk->expect.head = prev_check;
4257 continue;
4258 }
4259 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4260 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004261 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004262 return chk;
4263
4264 error:
4265 free_tcpcheck(chk, 0);
4266 free(str);
4267 free(comment);
4268 free(on_success_msg);
4269 free(on_error_msg);
4270 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004271 return NULL;
4272}
4273
Christopher Faulet61cc8522020-04-20 14:54:42 +02004274/* Overwrites fields of the old http send rule with those of the new one. When
4275 * replaced, old values are freed and replaced by the new ones. New values are
4276 * not copied but transferred. At the end <new> should be empty and can be
4277 * safely released. This function never fails.
4278 */
4279static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004280{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004281 struct logformat_node *lf, *lfb;
4282 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004283
Christopher Faulet404f9192020-04-09 23:13:54 +02004284
Christopher Faulet61cc8522020-04-20 14:54:42 +02004285 if (new->send.http.meth.str.area) {
4286 free(old->send.http.meth.str.area);
4287 old->send.http.meth.meth = new->send.http.meth.meth;
4288 old->send.http.meth.str.area = new->send.http.meth.str.area;
4289 old->send.http.meth.str.data = new->send.http.meth.str.data;
4290 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004291 }
4292
Christopher Faulet61cc8522020-04-20 14:54:42 +02004293 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4294 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004295 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004296 else
4297 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4298 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4299 old->send.http.uri = new->send.http.uri;
4300 new->send.http.uri = IST_NULL;
4301 }
4302 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4303 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004304 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004305 else
4306 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4307 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4308 LIST_INIT(&old->send.http.uri_fmt);
4309 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4310 LIST_DEL(&lf->list);
4311 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4312 }
4313 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004314
Christopher Faulet61cc8522020-04-20 14:54:42 +02004315 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004316 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004317 old->send.http.vsn = new->send.http.vsn;
4318 new->send.http.vsn = IST_NULL;
4319 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004320
Christopher Faulet61cc8522020-04-20 14:54:42 +02004321 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4322 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4323 LIST_DEL(&hdr->list);
4324 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004325 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004326
4327 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4328 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004329 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004330 else
4331 free_tcpcheck_fmt(&old->send.http.body_fmt);
4332 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4333 old->send.http.body = new->send.http.body;
4334 new->send.http.body = IST_NULL;
4335 }
4336 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4337 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004338 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004339 else
4340 free_tcpcheck_fmt(&old->send.http.body_fmt);
4341 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4342 LIST_INIT(&old->send.http.body_fmt);
4343 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4344 LIST_DEL(&lf->list);
4345 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4346 }
4347 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004348}
4349
Christopher Faulet61cc8522020-04-20 14:54:42 +02004350/* Internal function used to add an http-check rule in a list during the config
4351 * parsing step. Depending on its type, and the previously inserted rules, a
4352 * specific action may be performed or an error may be reported. This functions
4353 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4354 * message.
4355 */
4356static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004357{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004358 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004359
Christopher Faulet61cc8522020-04-20 14:54:42 +02004360 /* the implicit send rule coming from an "option httpchk" line must be
4361 * merged with the first explici http-check send rule, if
4362 * any. Depdending the declaration order some tests are required.
4363 *
4364 * Some tests is also required for other kinds of http-check rules to be
4365 * sure the ruleset remains valid.
4366 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004367
Christopher Faulet61cc8522020-04-20 14:54:42 +02004368 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4369 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4370 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4371 * following tests are performed :
4372 *
4373 * 1- If there is no such rule or if it is not a send rule, the implicit send
4374 * rule is pushed in front of the ruleset
4375 *
4376 * 2- If it is another implicit send rule, it is replaced with the new one.
4377 *
4378 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4379 * both, overwritting the old send rule (the explicit one) with info of the
4380 * new send rule (the implicit one).
4381 */
4382 r = get_first_tcpcheck_rule(rules);
4383 if (r && r->action == TCPCHK_ACT_CONNECT)
4384 r = get_next_tcpcheck_rule(rules, r);
4385 if (!r || r->action != TCPCHK_ACT_SEND)
4386 LIST_ADD(rules->list, &chk->list);
4387 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4388 LIST_DEL(&r->list);
4389 free_tcpcheck(r, 0);
4390 LIST_ADD(rules->list, &chk->list);
4391 }
4392 else {
4393 tcpcheck_overwrite_send_http_rule(r, chk);
4394 free_tcpcheck(chk, 0);
4395 }
4396 }
4397 else {
4398 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4399 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4400 * with an existing implicit send rule, if any. At the end, if there is no error,
4401 * the rule is appended to the list.
4402 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004403
Christopher Faulet61cc8522020-04-20 14:54:42 +02004404 r = get_last_tcpcheck_rule(rules);
4405 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4406 /* no error */;
4407 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4408 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4409 chk->index+1);
4410 return 0;
4411 }
4412 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4413 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4414 chk->index+1);
4415 return 0;
4416 }
4417 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4418 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4419 chk->index+1);
4420 return 0;
4421 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004422
Christopher Faulet61cc8522020-04-20 14:54:42 +02004423 if (chk->action == TCPCHK_ACT_SEND) {
4424 r = get_first_tcpcheck_rule(rules);
4425 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4426 tcpcheck_overwrite_send_http_rule(r, chk);
4427 free_tcpcheck(chk, 0);
4428 LIST_DEL(&r->list);
4429 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4430 chk = r;
4431 }
4432 }
4433 LIST_ADDQ(rules->list, &chk->list);
4434 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004435 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004436}
4437
Christopher Faulet61cc8522020-04-20 14:54:42 +02004438/**************************************************************************/
4439/************************** Init/deinit checks ****************************/
4440/**************************************************************************/
4441static const char *init_check(struct check *check, int type)
4442{
4443 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004444
Christopher Faulet61cc8522020-04-20 14:54:42 +02004445 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4446 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004447
Christopher Faulet61cc8522020-04-20 14:54:42 +02004448 check->bi.area = calloc(check->bi.size, sizeof(char));
4449 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004450
Christopher Faulet61cc8522020-04-20 14:54:42 +02004451 if (!check->bi.area || !check->bo.area)
4452 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004453
Christopher Faulet61cc8522020-04-20 14:54:42 +02004454 check->wait_list.tasklet = tasklet_new();
4455 if (!check->wait_list.tasklet)
4456 return "out of memory while allocating check tasklet";
4457 check->wait_list.events = 0;
4458 check->wait_list.tasklet->process = event_srv_chk_io;
4459 check->wait_list.tasklet->context = check;
4460 return NULL;
4461}
4462
4463void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004464{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004465 task_destroy(check->task);
4466 if (check->wait_list.tasklet)
4467 tasklet_free(check->wait_list.tasklet);
4468
4469 free(check->bi.area);
4470 free(check->bo.area);
4471 if (check->cs) {
4472 free(check->cs->conn);
4473 check->cs->conn = NULL;
4474 cs_free(check->cs);
4475 check->cs = NULL;
4476 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004477}
4478
Christopher Faulet61cc8522020-04-20 14:54:42 +02004479/* manages a server health-check. Returns the time the task accepts to wait, or
4480 * TIME_ETERNITY for infinity.
4481 */
4482static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004483{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004484 struct check *check = context;
4485
4486 if (check->type == PR_O2_EXT_CHK)
4487 return process_chk_proc(t, context, state);
4488 return process_chk_conn(t, context, state);
4489
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004490}
4491
Christopher Faulet61cc8522020-04-20 14:54:42 +02004492
4493static int start_check_task(struct check *check, int mininter,
4494 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004495{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004496 struct task *t;
4497 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004498
Christopher Faulet61cc8522020-04-20 14:54:42 +02004499 if (check->type == PR_O2_EXT_CHK)
4500 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004501
Christopher Faulet61cc8522020-04-20 14:54:42 +02004502 /* task for the check */
4503 if ((t = task_new(thread_mask)) == NULL) {
4504 ha_alert("Starting [%s:%s] check: out of memory.\n",
4505 check->server->proxy->id, check->server->id);
4506 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004507 }
4508
Christopher Faulet61cc8522020-04-20 14:54:42 +02004509 check->task = t;
4510 t->process = process_chk;
4511 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004512
Christopher Faulet61cc8522020-04-20 14:54:42 +02004513 if (mininter < srv_getinter(check))
4514 mininter = srv_getinter(check);
4515
4516 if (global.max_spread_checks && mininter > global.max_spread_checks)
4517 mininter = global.max_spread_checks;
4518
4519 /* check this every ms */
4520 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4521 check->start = now;
4522 task_queue(t);
4523
4524 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004525}
4526
Christopher Faulet61cc8522020-04-20 14:54:42 +02004527/* updates the server's weight during a warmup stage. Once the final weight is
4528 * reached, the task automatically stops. Note that any server status change
4529 * must have updated s->last_change accordingly.
4530 */
4531static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004532{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004533 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004534
Christopher Faulet61cc8522020-04-20 14:54:42 +02004535 /* by default, plan on stopping the task */
4536 t->expire = TICK_ETERNITY;
4537 if ((s->next_admin & SRV_ADMF_MAINT) ||
4538 (s->next_state != SRV_ST_STARTING))
4539 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004540
Christopher Faulet61cc8522020-04-20 14:54:42 +02004541 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004542
Christopher Faulet61cc8522020-04-20 14:54:42 +02004543 /* recalculate the weights and update the state */
4544 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004545
Christopher Faulet61cc8522020-04-20 14:54:42 +02004546 /* probably that we can refill this server with a bit more connections */
4547 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004548
Christopher Faulet61cc8522020-04-20 14:54:42 +02004549 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004550
Christopher Faulet61cc8522020-04-20 14:54:42 +02004551 /* get back there in 1 second or 1/20th of the slowstart interval,
4552 * whichever is greater, resulting in small 5% steps.
4553 */
4554 if (s->next_state == SRV_ST_STARTING)
4555 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4556 return t;
4557}
4558
4559/*
4560 * Start health-check.
4561 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4562 */
4563static int start_checks()
4564{
4565
4566 struct proxy *px;
4567 struct server *s;
4568 struct task *t;
4569 int nbcheck=0, mininter=0, srvpos=0;
4570
4571 /* 0- init the dummy frontend used to create all checks sessions */
4572 init_new_proxy(&checks_fe);
4573 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4574 checks_fe.mode = PR_MODE_TCP;
4575 checks_fe.maxconn = 0;
4576 checks_fe.conn_retries = CONN_RETRIES;
4577 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4578 checks_fe.timeout.client = TICK_ETERNITY;
4579
4580 /* 1- count the checkers to run simultaneously.
4581 * We also determine the minimum interval among all of those which
4582 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4583 * will be used to spread their start-up date. Those which have
4584 * a shorter interval will start independently and will not dictate
4585 * too short an interval for all others.
4586 */
4587 for (px = proxies_list; px; px = px->next) {
4588 for (s = px->srv; s; s = s->next) {
4589 if (s->slowstart) {
4590 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4591 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4592 return ERR_ALERT | ERR_FATAL;
4593 }
4594 /* We need a warmup task that will be called when the server
4595 * state switches from down to up.
4596 */
4597 s->warmup = t;
4598 t->process = server_warmup;
4599 t->context = s;
4600 /* server can be in this state only because of */
4601 if (s->next_state == SRV_ST_STARTING)
4602 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 +02004603 }
4604
Christopher Faulet61cc8522020-04-20 14:54:42 +02004605 if (s->check.state & CHK_ST_CONFIGURED) {
4606 nbcheck++;
4607 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4608 (!mininter || mininter > srv_getinter(&s->check)))
4609 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004610 }
4611
Christopher Faulet61cc8522020-04-20 14:54:42 +02004612 if (s->agent.state & CHK_ST_CONFIGURED) {
4613 nbcheck++;
4614 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4615 (!mininter || mininter > srv_getinter(&s->agent)))
4616 mininter = srv_getinter(&s->agent);
4617 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004618 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004619 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004620
Christopher Faulet61cc8522020-04-20 14:54:42 +02004621 if (!nbcheck)
4622 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004623
Christopher Faulet61cc8522020-04-20 14:54:42 +02004624 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004625
Christopher Faulet61cc8522020-04-20 14:54:42 +02004626 /*
4627 * 2- start them as far as possible from each others. For this, we will
4628 * start them after their interval set to the min interval divided by
4629 * the number of servers, weighted by the server's position in the list.
4630 */
4631 for (px = proxies_list; px; px = px->next) {
4632 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4633 if (init_pid_list()) {
4634 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4635 return ERR_ALERT | ERR_FATAL;
4636 }
4637 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004638
Christopher Faulet61cc8522020-04-20 14:54:42 +02004639 for (s = px->srv; s; s = s->next) {
4640 /* A task for the main check */
4641 if (s->check.state & CHK_ST_CONFIGURED) {
4642 if (s->check.type == PR_O2_EXT_CHK) {
4643 if (!prepare_external_check(&s->check))
4644 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004645 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004646 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4647 return ERR_ALERT | ERR_FATAL;
4648 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004649 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004650
Christopher Faulet61cc8522020-04-20 14:54:42 +02004651 /* A task for a auxiliary agent check */
4652 if (s->agent.state & CHK_ST_CONFIGURED) {
4653 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4654 return ERR_ALERT | ERR_FATAL;
4655 }
4656 srvpos++;
4657 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004658 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004659 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004660 return 0;
4661}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004662
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004663
Christopher Faulet61cc8522020-04-20 14:54:42 +02004664/*
4665 * Return value:
4666 * the port to be used for the health check
4667 * 0 in case no port could be found for the check
4668 */
4669static int srv_check_healthcheck_port(struct check *chk)
4670{
4671 int i = 0;
4672 struct server *srv = NULL;
4673
4674 srv = chk->server;
4675
4676 /* by default, we use the health check port ocnfigured */
4677 if (chk->port > 0)
4678 return chk->port;
4679
4680 /* try to get the port from check_core.addr if check.port not set */
4681 i = get_host_port(&chk->addr);
4682 if (i > 0)
4683 return i;
4684
4685 /* try to get the port from server address */
4686 /* prevent MAPPORTS from working at this point, since checks could
4687 * not be performed in such case (MAPPORTS impose a relative ports
4688 * based on live traffic)
4689 */
4690 if (srv->flags & SRV_F_MAPPORTS)
4691 return 0;
4692
4693 i = srv->svc_port; /* by default */
4694 if (i > 0)
4695 return i;
4696
4697 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004698}
4699
Christopher Faulet61cc8522020-04-20 14:54:42 +02004700/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4701 * if an error occurred.
4702 */
4703static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004704{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004705 const char *err;
4706 struct tcpcheck_rule *r;
4707 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004708
Christopher Faulet61cc8522020-04-20 14:54:42 +02004709 if (!srv->do_check)
4710 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004711
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004712
Christopher Faulet61cc8522020-04-20 14:54:42 +02004713 /* If neither a port nor an addr was specified and no check transport
4714 * layer is forced, then the transport layer used by the checks is the
4715 * same as for the production traffic. Otherwise we use raw_sock by
4716 * default, unless one is specified.
4717 */
4718 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4719 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4720 srv->check.use_ssl = srv->use_ssl;
4721 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004722 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004723 else if (srv->check.use_ssl == 1)
4724 srv->check.xprt = xprt_get(XPRT_SSL);
4725 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004726 }
4727
Christopher Faulet12882cf2020-04-23 15:50:18 +02004728 /* Inherit the mux protocol from the server if not already defined for
4729 * the check
4730 */
4731 if (srv->mux_proto && !srv->check.mux_proto)
4732 srv->check.mux_proto = srv->mux_proto;
4733
Christopher Faulet61cc8522020-04-20 14:54:42 +02004734 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004735
Christopher Faulet61cc8522020-04-20 14:54:42 +02004736 /* We need at least a service port, a check port or the first tcp-check
4737 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4738 */
4739 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4740 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4741 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004742
Christopher Faulet61cc8522020-04-20 14:54:42 +02004743 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4744 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4745 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4746 ret |= ERR_ALERT | ERR_ABORT;
4747 goto out;
4748 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004749
Christopher Faulet61cc8522020-04-20 14:54:42 +02004750 /* search the first action (connect / send / expect) in the list */
4751 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4752 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4753 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4754 "nor tcp_check rule 'connect' with port information.\n",
4755 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4756 ret |= ERR_ALERT | ERR_ABORT;
4757 goto out;
4758 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004759
Christopher Faulet61cc8522020-04-20 14:54:42 +02004760 /* scan the tcp-check ruleset to ensure a port has been configured */
4761 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4762 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4763 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4764 "and a tcp_check rule 'connect' with no port information.\n",
4765 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4766 ret |= ERR_ALERT | ERR_ABORT;
4767 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004768 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004769 }
4770
Christopher Faulet61cc8522020-04-20 14:54:42 +02004771 init:
4772 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4773 struct tcpcheck_ruleset *rs = NULL;
4774 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4775 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004776
Christopher Faulet61cc8522020-04-20 14:54:42 +02004777 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4778 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004779
Christopher Faulet61cc8522020-04-20 14:54:42 +02004780 rs = find_tcpcheck_ruleset("*tcp-check");
4781 if (!rs) {
4782 rs = create_tcpcheck_ruleset("*tcp-check");
4783 if (rs == NULL) {
4784 ha_alert("config: %s '%s': out of memory.\n",
4785 proxy_type_str(srv->proxy), srv->proxy->id);
4786 ret |= ERR_ALERT | ERR_FATAL;
4787 goto out;
4788 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004789 }
4790
Christopher Faulet61cc8522020-04-20 14:54:42 +02004791 free_tcpcheck_vars(&rules->preset_vars);
4792 rules->list = &rs->rules;
4793 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004794 }
4795
Christopher Faulet61cc8522020-04-20 14:54:42 +02004796 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4797 if (err) {
4798 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4799 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4800 ret |= ERR_ALERT | ERR_ABORT;
4801 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004802 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004803 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4804 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004805
Christopher Faulet61cc8522020-04-20 14:54:42 +02004806 out:
4807 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004808}
4809
Christopher Faulet61cc8522020-04-20 14:54:42 +02004810/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4811 * if an error occurred.
4812 */
4813static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004814{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004815 struct tcpcheck_rule *chk;
4816 const char *err;
4817 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004818
Christopher Faulet61cc8522020-04-20 14:54:42 +02004819 if (!srv->do_agent)
4820 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004821
Christopher Faulet61cc8522020-04-20 14:54:42 +02004822 /* If there is no connect rule preceeding all send / expect rules, an
4823 * implicit one is inserted before all others.
4824 */
4825 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4826 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4827 chk = calloc(1, sizeof(*chk));
4828 if (!chk) {
4829 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4830 " to agent-check for server '%s' (out of memory).\n",
4831 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4832 ret |= ERR_ALERT | ERR_FATAL;
4833 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004834 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004835 chk->action = TCPCHK_ACT_CONNECT;
4836 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4837 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004838 }
4839
Christopher Faulete5870d82020-04-15 11:32:03 +02004840
Christopher Faulet61cc8522020-04-20 14:54:42 +02004841 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4842 if (err) {
4843 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4844 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4845 ret |= ERR_ALERT | ERR_ABORT;
4846 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004847 }
4848
Christopher Faulet61cc8522020-04-20 14:54:42 +02004849 if (!srv->agent.inter)
4850 srv->agent.inter = srv->check.inter;
4851
4852 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4853 global.maxsock++;
4854
4855 out:
4856 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004857}
4858
Christopher Faulet61cc8522020-04-20 14:54:42 +02004859/* Check tcp-check health-check configuration for the proxy <px>. */
4860static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004861{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004862 struct tcpcheck_rule *chk, *back;
4863 char *comment = NULL, *errmsg = NULL;
4864 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4865 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004866
Christopher Faulet61cc8522020-04-20 14:54:42 +02004867 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4868 deinit_proxy_tcpcheck(px);
4869 goto out;
4870 }
4871
4872 free(px->check_command);
4873 free(px->check_path);
4874 px->check_command = px->check_path = NULL;
4875
4876 if (!px->tcpcheck_rules.list) {
4877 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4878 ret |= ERR_ALERT | ERR_FATAL;
4879 goto out;
4880 }
4881
4882 /* HTTP ruleset only : */
4883 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4884 struct tcpcheck_rule *next;
4885
4886 /* move remaining implicit send rule from "option httpchk" line to the right place.
4887 * If such rule exists, it must be the first one. In this case, the rule is moved
4888 * after the first connect rule, if any. Otherwise, nothing is done.
4889 */
4890 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4891 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4892 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4893 if (next && next->action == TCPCHK_ACT_CONNECT) {
4894 LIST_DEL(&chk->list);
4895 LIST_ADD(&next->list, &chk->list);
4896 chk->index = next->index;
4897 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004898 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004899
4900 /* add implicit expect rule if the last one is a send. It is inherited from previous
4901 * versions where the http expect rule was optional. Now it is possible to chained
4902 * send/expect rules but the last expect may still be implicit.
4903 */
4904 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4905 if (chk && chk->action == TCPCHK_ACT_SEND) {
4906 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
4907 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4908 px->conf.file, px->conf.line, &errmsg);
4909 if (!next) {
4910 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4911 "(%s).\n", px->id, errmsg);
4912 free(errmsg);
4913 ret |= ERR_ALERT | ERR_FATAL;
4914 goto out;
4915 }
4916 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4917 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004918 }
4919 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004920
4921 /* For all ruleset: */
4922
4923 /* If there is no connect rule preceeding all send / expect rules, an
4924 * implicit one is inserted before all others.
4925 */
4926 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4927 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4928 chk = calloc(1, sizeof(*chk));
4929 if (!chk) {
4930 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4931 "(out of memory).\n", px->id);
4932 ret |= ERR_ALERT | ERR_FATAL;
4933 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004934 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004935 chk->action = TCPCHK_ACT_CONNECT;
4936 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4937 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4938 }
4939
4940 /* Remove all comment rules. To do so, when a such rule is found, the
4941 * comment is assigned to the following rule(s).
4942 */
4943 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4944 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4945 free(comment);
4946 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004947 }
4948
Christopher Faulet61cc8522020-04-20 14:54:42 +02004949 prev_action = chk->action;
4950 switch (chk->action) {
4951 case TCPCHK_ACT_COMMENT:
4952 free(comment);
4953 comment = chk->comment;
4954 LIST_DEL(&chk->list);
4955 free(chk);
4956 break;
4957 case TCPCHK_ACT_CONNECT:
4958 if (!chk->comment && comment)
4959 chk->comment = strdup(comment);
4960 /* fall though */
4961 case TCPCHK_ACT_ACTION_KW:
4962 free(comment);
4963 comment = NULL;
4964 break;
4965 case TCPCHK_ACT_SEND:
4966 case TCPCHK_ACT_EXPECT:
4967 if (!chk->comment && comment)
4968 chk->comment = strdup(comment);
4969 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004970 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004971 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004972 free(comment);
4973 comment = NULL;
4974
4975 out:
4976 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004977}
4978
Christopher Faulet61cc8522020-04-20 14:54:42 +02004979void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004980{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004981 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
4982 px->tcpcheck_rules.flags = 0;
4983 px->tcpcheck_rules.list = NULL;
4984}
Christopher Faulete5870d82020-04-15 11:32:03 +02004985
Christopher Faulet61cc8522020-04-20 14:54:42 +02004986static void deinit_srv_check(struct server *srv)
4987{
4988 if (srv->check.state & CHK_ST_CONFIGURED)
4989 free_check(&srv->check);
4990 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4991 srv->do_check = 0;
4992}
Christopher Faulete5870d82020-04-15 11:32:03 +02004993
Christopher Faulet61cc8522020-04-20 14:54:42 +02004994
4995static void deinit_srv_agent_check(struct server *srv)
4996{
4997 if (srv->agent.tcpcheck_rules) {
4998 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4999 free(srv->agent.tcpcheck_rules);
5000 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005001 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005002
Christopher Faulet61cc8522020-04-20 14:54:42 +02005003 if (srv->agent.state & CHK_ST_CONFIGURED)
5004 free_check(&srv->agent);
5005
5006 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5007 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005008}
5009
Christopher Faulet61cc8522020-04-20 14:54:42 +02005010static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005011{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005012 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005013 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005014 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005015
Christopher Fauletd7cee712020-04-21 13:45:00 +02005016 node = ebpt_first(&shared_tcpchecks);
5017 while (node) {
5018 next = ebpt_next(node);
5019 ebpt_delete(node);
5020 free(node->key);
5021 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005022 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5023 LIST_DEL(&r->list);
5024 free_tcpcheck(r, 0);
5025 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005026 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005027 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005028 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005029}
Christopher Faulete5870d82020-04-15 11:32:03 +02005030
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005031
Christopher Faulet61cc8522020-04-20 14:54:42 +02005032REGISTER_POST_SERVER_CHECK(init_srv_check);
5033REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5034REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5035REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005036
Christopher Faulet61cc8522020-04-20 14:54:42 +02005037REGISTER_SERVER_DEINIT(deinit_srv_check);
5038REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5039REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5040REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005041
Christopher Faulet61cc8522020-04-20 14:54:42 +02005042/**************************************************************************/
5043/****************************** Email alerts ******************************/
5044/* NOTE: It may be pertinent to use an applet to handle email alerts */
5045/* instead of a tcp-check ruleset */
5046/**************************************************************************/
5047void email_alert_free(struct email_alert *alert)
5048{
5049 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005050
Christopher Faulet61cc8522020-04-20 14:54:42 +02005051 if (!alert)
5052 return;
5053
5054 if (alert->rules.list) {
5055 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5056 LIST_DEL(&rule->list);
5057 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005058 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005059 free_tcpcheck_vars(&alert->rules.preset_vars);
5060 free(alert->rules.list);
5061 alert->rules.list = NULL;
5062 }
5063 pool_free(pool_head_email_alert, alert);
5064}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005065
Christopher Faulet61cc8522020-04-20 14:54:42 +02005066static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5067{
5068 struct check *check = context;
5069 struct email_alertq *q;
5070 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005071
Christopher Faulet61cc8522020-04-20 14:54:42 +02005072 q = container_of(check, typeof(*q), check);
5073
5074 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5075 while (1) {
5076 if (!(check->state & CHK_ST_ENABLED)) {
5077 if (LIST_ISEMPTY(&q->email_alerts)) {
5078 /* All alerts processed, queue the task */
5079 t->expire = TICK_ETERNITY;
5080 task_queue(t);
5081 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005082 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005083
5084 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5085 LIST_DEL(&alert->list);
5086 t->expire = now_ms;
5087 check->tcpcheck_rules = &alert->rules;
5088 check->status = HCHK_STATUS_INI;
5089 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005090 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005091
5092 process_chk(t, context, state);
5093 if (check->state & CHK_ST_INPROGRESS)
5094 break;
5095
5096 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5097 email_alert_free(alert);
5098 check->tcpcheck_rules = NULL;
5099 check->server = NULL;
5100 check->state &= ~CHK_ST_ENABLED;
5101 }
5102 end:
5103 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5104 return t;
5105}
5106
5107/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5108 *
5109 * The function returns 1 in success case, otherwise, it returns 0 and err is
5110 * filled.
5111 */
5112int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5113{
5114 struct mailer *mailer;
5115 struct email_alertq *queues;
5116 const char *err_str;
5117 int i = 0;
5118
5119 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5120 memprintf(err, "out of memory while allocating mailer alerts queues");
5121 goto fail_no_queue;
5122 }
5123
5124 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5125 struct email_alertq *q = &queues[i];
5126 struct check *check = &q->check;
5127 struct task *t;
5128
5129 LIST_INIT(&q->email_alerts);
5130 HA_SPIN_INIT(&q->lock);
5131 check->inter = mls->timeout.mail;
5132 check->rise = DEF_AGENT_RISETIME;
5133 check->proxy = p;
5134 check->fall = DEF_AGENT_FALLTIME;
5135 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5136 memprintf(err, "%s", err_str);
5137 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005138 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005139
5140 check->xprt = mailer->xprt;
5141 check->addr = mailer->addr;
5142 check->port = get_host_port(&mailer->addr);
5143
5144 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5145 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005146 goto error;
5147 }
5148
Christopher Faulet61cc8522020-04-20 14:54:42 +02005149 check->task = t;
5150 t->process = process_email_alert;
5151 t->context = check;
5152
5153 /* check this in one ms */
5154 t->expire = TICK_ETERNITY;
5155 check->start = now;
5156 task_queue(t);
5157 }
5158
5159 mls->users++;
5160 free(p->email_alert.mailers.name);
5161 p->email_alert.mailers.m = mls;
5162 p->email_alert.queues = queues;
5163 return 0;
5164
5165 error:
5166 for (i = 0; i < mls->count; i++) {
5167 struct email_alertq *q = &queues[i];
5168 struct check *check = &q->check;
5169
5170 free_check(check);
5171 }
5172 free(queues);
5173 fail_no_queue:
5174 return 1;
5175}
5176
5177static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5178{
5179 struct tcpcheck_rule *tcpcheck, *prev_check;
5180 struct tcpcheck_expect *expect;
5181
5182 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5183 return 0;
5184 memset(tcpcheck, 0, sizeof(*tcpcheck));
5185 tcpcheck->action = TCPCHK_ACT_EXPECT;
5186
5187 expect = &tcpcheck->expect;
5188 expect->type = TCPCHK_EXPECT_STRING;
5189 LIST_INIT(&expect->onerror_fmt);
5190 LIST_INIT(&expect->onsuccess_fmt);
5191 expect->ok_status = HCHK_STATUS_L7OKD;
5192 expect->err_status = HCHK_STATUS_L7RSP;
5193 expect->tout_status = HCHK_STATUS_L7TOUT;
5194 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005195 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005196 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5197 return 0;
5198 }
5199
5200 /* All tcp-check expect points back to the first inverse expect rule
5201 * in a chain of one or more expect rule, potentially itself.
5202 */
5203 tcpcheck->expect.head = tcpcheck;
5204 list_for_each_entry_rev(prev_check, rules->list, list) {
5205 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5206 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5207 tcpcheck->expect.head = prev_check;
5208 continue;
5209 }
5210 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5211 break;
5212 }
5213 LIST_ADDQ(rules->list, &tcpcheck->list);
5214 return 1;
5215}
5216
5217static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5218{
5219 struct tcpcheck_rule *tcpcheck;
5220 struct tcpcheck_send *send;
5221 const char *in;
5222 char *dst;
5223 int i;
5224
5225 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5226 return 0;
5227 memset(tcpcheck, 0, sizeof(*tcpcheck));
5228 tcpcheck->action = TCPCHK_ACT_SEND;
5229
5230 send = &tcpcheck->send;
5231 send->type = TCPCHK_SEND_STRING;
5232
5233 for (i = 0; strs[i]; i++)
5234 send->data.len += strlen(strs[i]);
5235
Christopher Fauletb61caf42020-04-21 10:57:42 +02005236 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005237 if (!isttest(send->data)) {
5238 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5239 return 0;
5240 }
5241
Christopher Fauletb61caf42020-04-21 10:57:42 +02005242 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005243 for (i = 0; strs[i]; i++)
5244 for (in = strs[i]; (*dst = *in++); dst++);
5245 *dst = 0;
5246
5247 LIST_ADDQ(rules->list, &tcpcheck->list);
5248 return 1;
5249}
5250
5251static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5252 struct email_alertq *q, const char *msg)
5253{
5254 struct email_alert *alert;
5255 struct tcpcheck_rule *tcpcheck;
5256 struct check *check = &q->check;
5257
5258 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5259 goto error;
5260 LIST_INIT(&alert->list);
5261 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5262 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5263 if (!alert->rules.list)
5264 goto error;
5265 LIST_INIT(alert->rules.list);
5266 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5267 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005268
Christopher Faulet61cc8522020-04-20 14:54:42 +02005269 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5270 goto error;
5271 memset(tcpcheck, 0, sizeof(*tcpcheck));
5272 tcpcheck->action = TCPCHK_ACT_CONNECT;
5273 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005274
Christopher Faulet61cc8522020-04-20 14:54:42 +02005275 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005276
Christopher Faulet61cc8522020-04-20 14:54:42 +02005277 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005278 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005279
5280 {
5281 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5282 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5283 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005284 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005285
Christopher Faulet61cc8522020-04-20 14:54:42 +02005286 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5287 goto error;
5288
5289 {
5290 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5291 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005292 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005293 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005294
5295 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5296 goto error;
5297
5298 {
5299 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5300 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005301 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005302 }
5303
Christopher Faulet61cc8522020-04-20 14:54:42 +02005304 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5305 goto error;
5306
5307 {
5308 const char * const strs[2] = { "DATA\r\n" };
5309 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005310 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005311 }
5312
5313 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5314 goto error;
5315
5316 {
5317 struct tm tm;
5318 char datestr[48];
5319 const char * const strs[18] = {
5320 "From: ", p->email_alert.from, "\r\n",
5321 "To: ", p->email_alert.to, "\r\n",
5322 "Date: ", datestr, "\r\n",
5323 "Subject: [HAproxy Alert] ", msg, "\r\n",
5324 "\r\n",
5325 msg, "\r\n",
5326 "\r\n",
5327 ".\r\n",
5328 NULL
5329 };
5330
5331 get_localtime(date.tv_sec, &tm);
5332
5333 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005334 goto error;
5335 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005336
5337 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005338 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005339 }
5340
5341 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005342 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005343
5344 {
5345 const char * const strs[2] = { "QUIT\r\n" };
5346 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5347 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005348 }
5349
Christopher Faulet61cc8522020-04-20 14:54:42 +02005350 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5351 goto error;
5352
5353 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5354 task_wakeup(check->task, TASK_WOKEN_MSG);
5355 LIST_ADDQ(&q->email_alerts, &alert->list);
5356 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5357 return 1;
5358
5359error:
5360 email_alert_free(alert);
5361 return 0;
5362}
5363
5364static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5365{
5366 int i;
5367 struct mailer *mailer;
5368
5369 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5370 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5371 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5372 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5373 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005374 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005375 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005376
Christopher Faulet61cc8522020-04-20 14:54:42 +02005377 return;
5378}
5379
5380/*
5381 * Send email alert if configured.
5382 */
5383void send_email_alert(struct server *s, int level, const char *format, ...)
5384{
5385 va_list argp;
5386 char buf[1024];
5387 int len;
5388 struct proxy *p = s->proxy;
5389
5390 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5391 return;
5392
5393 va_start(argp, format);
5394 len = vsnprintf(buf, sizeof(buf), format, argp);
5395 va_end(argp);
5396
5397 if (len < 0 || len >= sizeof(buf)) {
5398 ha_alert("Email alert [%s] could not format message\n", p->id);
5399 return;
5400 }
5401
5402 enqueue_email_alert(p, s, buf);
5403}
5404
5405/**************************************************************************/
5406/************************** Check sample fetches **************************/
5407/**************************************************************************/
5408/* extracts check payload at a fixed position and length */
5409static int
5410smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5411{
5412 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5413 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5414 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5415 struct buffer *buf;
5416
5417 if (!check)
5418 return 0;
5419
5420 buf = &check->bi;
5421 if (buf_offset > b_data(buf))
5422 goto no_match;
5423 if (buf_offset + buf_size > b_data(buf))
5424 buf_size = 0;
5425
5426 /* init chunk as read only */
5427 smp->data.type = SMP_T_STR;
5428 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5429 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5430
5431 return 1;
5432
5433 no_match:
5434 smp->flags = 0;
5435 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005436}
5437
Christopher Faulet61cc8522020-04-20 14:54:42 +02005438static struct sample_fetch_kw_list smp_kws = {ILH, {
5439 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5440 { /* END */ },
5441}};
5442
5443INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5444
5445
5446/**************************************************************************/
5447/************************ Check's parsing functions ***********************/
5448/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005449/* Parses the "tcp-check" proxy keyword */
5450static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5451 struct proxy *defpx, const char *file, int line,
5452 char **errmsg)
5453{
Christopher Faulet404f9192020-04-09 23:13:54 +02005454 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005455 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005456 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005457
5458 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5459 ret = 1;
5460
Christopher Faulet404f9192020-04-09 23:13:54 +02005461 /* Deduce the ruleset name from the proxy info */
5462 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5463 ((curpx == defpx) ? "defaults" : curpx->id),
5464 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005465
Christopher Faulet61cc8522020-04-20 14:54:42 +02005466 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005467 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005468 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005469 if (rs == NULL) {
5470 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005471 goto error;
5472 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005473 }
5474
Gaetan Rivet5301b012020-02-25 17:19:17 +01005475 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005476 if (!LIST_ISEMPTY(&rs->rules)) {
5477 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005478 index = chk->index + 1;
5479 }
5480
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005481 cur_arg = 1;
5482 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005483 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005484 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005485 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005486 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005487 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005488 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005489 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005490 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005491 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5492
5493 if (!kw) {
5494 action_kw_tcp_check_build_list(&trash);
5495 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5496 "%s%s. but got '%s'",
5497 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5498 goto error;
5499 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005500 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005501 }
5502
5503 if (!chk) {
5504 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5505 goto error;
5506 }
5507 ret = (*errmsg != NULL); /* Handle warning */
5508
5509 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005510 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005511 LIST_ADDQ(&rs->rules, &chk->list);
5512
5513 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005514 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005515 /* Use this ruleset if the proxy already has tcp-check enabled */
5516 curpx->tcpcheck_rules.list = &rs->rules;
5517 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5518 }
5519 else {
5520 /* mark this ruleset as unused for now */
5521 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5522 }
5523
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005524 return ret;
5525
5526 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005527 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005528 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005529 return -1;
5530}
5531
Christopher Faulet51b129f2020-04-09 15:54:18 +02005532/* Parses the "http-check" proxy keyword */
5533static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5534 struct proxy *defpx, const char *file, int line,
5535 char **errmsg)
5536{
Christopher Faulete5870d82020-04-15 11:32:03 +02005537 struct tcpcheck_ruleset *rs = NULL;
5538 struct tcpcheck_rule *chk = NULL;
5539 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005540
5541 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5542 ret = 1;
5543
5544 cur_arg = 1;
5545 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5546 /* enable a graceful server shutdown on an HTTP 404 response */
5547 curpx->options |= PR_O_DISABLE404;
5548 if (too_many_args(1, args, errmsg, NULL))
5549 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005550 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005551 }
5552 else if (strcmp(args[cur_arg], "send-state") == 0) {
5553 /* enable emission of the apparent state of a server in HTTP checks */
5554 curpx->options2 |= PR_O2_CHK_SNDST;
5555 if (too_many_args(1, args, errmsg, NULL))
5556 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005557 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005558 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005559
Christopher Faulete5870d82020-04-15 11:32:03 +02005560 /* Deduce the ruleset name from the proxy info */
5561 chunk_printf(&trash, "*http-check-%s_%s-%d",
5562 ((curpx == defpx) ? "defaults" : curpx->id),
5563 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005564
Christopher Faulet61cc8522020-04-20 14:54:42 +02005565 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005566 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005567 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005568 if (rs == NULL) {
5569 memprintf(errmsg, "out of memory.\n");
5570 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005571 }
5572 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005573
Christopher Faulete5870d82020-04-15 11:32:03 +02005574 index = 0;
5575 if (!LIST_ISEMPTY(&rs->rules)) {
5576 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5577 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5578 index = chk->index + 1;
5579 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005580
Christopher Faulete5870d82020-04-15 11:32:03 +02005581 if (strcmp(args[cur_arg], "connect") == 0)
5582 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5583 else if (strcmp(args[cur_arg], "send") == 0)
5584 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5585 else if (strcmp(args[cur_arg], "expect") == 0)
5586 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5587 file, line, errmsg);
5588 else if (strcmp(args[cur_arg], "comment") == 0)
5589 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5590 else {
5591 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005592
Christopher Faulete5870d82020-04-15 11:32:03 +02005593 if (!kw) {
5594 action_kw_tcp_check_build_list(&trash);
5595 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5596 " 'send', 'expect'%s%s. but got '%s'",
5597 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5598 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005599 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005600 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5601 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005602
Christopher Faulete5870d82020-04-15 11:32:03 +02005603 if (!chk) {
5604 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5605 goto error;
5606 }
5607 ret = (*errmsg != NULL); /* Handle warning */
5608
5609 chk->index = index;
5610 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5611 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5612 /* Use this ruleset if the proxy already has http-check enabled */
5613 curpx->tcpcheck_rules.list = &rs->rules;
5614 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5615 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5616 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5617 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005618 goto error;
5619 }
5620 }
5621 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005622 /* mark this ruleset as unused for now */
5623 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5624 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005625 }
5626
Christopher Faulete5870d82020-04-15 11:32:03 +02005627 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005628 return ret;
5629
5630 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005631 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005632 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005633 return -1;
5634}
5635
Christopher Faulete9111b62020-04-09 18:12:08 +02005636/* Parses the "external-check" proxy keyword */
5637static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5638 struct proxy *defpx, const char *file, int line,
5639 char **errmsg)
5640{
5641 int cur_arg, ret = 0;
5642
5643 cur_arg = 1;
5644 if (!*(args[cur_arg])) {
5645 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5646 goto error;
5647 }
5648
5649 if (strcmp(args[cur_arg], "command") == 0) {
5650 if (too_many_args(2, args, errmsg, NULL))
5651 goto error;
5652 if (!*(args[cur_arg+1])) {
5653 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5654 goto error;
5655 }
5656 free(curpx->check_command);
5657 curpx->check_command = strdup(args[cur_arg+1]);
5658 }
5659 else if (strcmp(args[cur_arg], "path") == 0) {
5660 if (too_many_args(2, args, errmsg, NULL))
5661 goto error;
5662 if (!*(args[cur_arg+1])) {
5663 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5664 goto error;
5665 }
5666 free(curpx->check_path);
5667 curpx->check_path = strdup(args[cur_arg+1]);
5668 }
5669 else {
5670 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5671 args[0], args[1]);
5672 goto error;
5673 }
5674
5675 ret = (*errmsg != NULL); /* Handle warning */
5676 return ret;
5677
5678error:
5679 return -1;
5680}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005681
Christopher Faulet430e4802020-04-09 15:28:16 +02005682/* Parses the "option tcp-check" proxy keyword */
5683int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5684 const char *file, int line)
5685{
Christopher Faulet404f9192020-04-09 23:13:54 +02005686 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005687 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5688 int err_code = 0;
5689
5690 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5691 err_code |= ERR_WARN;
5692
5693 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5694 goto out;
5695
Christopher Faulet404f9192020-04-09 23:13:54 +02005696 curpx->options2 &= ~PR_O2_CHK_ANY;
5697 curpx->options2 |= PR_O2_TCPCHK_CHK;
5698
Christopher Fauletd7e63962020-04-17 20:15:59 +02005699 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005700 /* If a tcp-check rulesset is already set, do nothing */
5701 if (rules->list)
5702 goto out;
5703
5704 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5705 * get it.
5706 */
5707 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5708 goto curpx_ruleset;
5709
5710 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5711 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005712 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005713 if (rs)
5714 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005715 }
5716
Christopher Faulet404f9192020-04-09 23:13:54 +02005717 curpx_ruleset:
5718 /* Deduce the ruleset name from the proxy info */
5719 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5720 ((curpx == defpx) ? "defaults" : curpx->id),
5721 curpx->conf.file, curpx->conf.line);
5722
Christopher Faulet61cc8522020-04-20 14:54:42 +02005723 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005724 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005725 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005726 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005727 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5728 goto error;
5729 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005730 }
5731
Christopher Faulet404f9192020-04-09 23:13:54 +02005732 ruleset_found:
5733 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005734 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005735 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005736
5737 out:
5738 return err_code;
5739
5740 error:
5741 err_code |= ERR_ALERT | ERR_FATAL;
5742 goto out;
5743}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005744
5745/* Parses the "option redis-check" proxy keyword */
5746int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5747 const char *file, int line)
5748{
5749 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5750 static char *redis_res = "+PONG\r\n";
5751
5752 struct tcpcheck_ruleset *rs = NULL;
5753 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5754 struct tcpcheck_rule *chk;
5755 char *errmsg = NULL;
5756 int err_code = 0;
5757
5758 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5759 err_code |= ERR_WARN;
5760
5761 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5762 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005763
5764 curpx->options2 &= ~PR_O2_CHK_ANY;
5765 curpx->options2 |= PR_O2_TCPCHK_CHK;
5766
5767 free_tcpcheck_vars(&rules->preset_vars);
5768 rules->list = NULL;
5769 rules->flags = 0;
5770
Christopher Faulet61cc8522020-04-20 14:54:42 +02005771 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005772 if (rs)
5773 goto ruleset_found;
5774
Christopher Faulet61cc8522020-04-20 14:54:42 +02005775 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005776 if (rs == NULL) {
5777 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5778 goto error;
5779 }
5780
5781 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5782 1, curpx, &rs->rules, file, line, &errmsg);
5783 if (!chk) {
5784 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5785 goto error;
5786 }
5787 chk->index = 0;
5788 LIST_ADDQ(&rs->rules, &chk->list);
5789
5790 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5791 "error-status", "L7STS",
5792 "on-error", "%[check.payload(),cut_crlf]",
5793 "on-success", "Redis server is ok",
5794 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005795 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005796 if (!chk) {
5797 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5798 goto error;
5799 }
5800 chk->index = 1;
5801 LIST_ADDQ(&rs->rules, &chk->list);
5802
Christopher Fauletd7cee712020-04-21 13:45:00 +02005803 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005804
5805 ruleset_found:
5806 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005807 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005808
5809 out:
5810 free(errmsg);
5811 return err_code;
5812
5813 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005814 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005815 err_code |= ERR_ALERT | ERR_FATAL;
5816 goto out;
5817}
5818
Christopher Faulet811f78c2020-04-01 11:10:27 +02005819
5820/* Parses the "option ssl-hello-chk" proxy keyword */
5821int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5822 const char *file, int line)
5823{
5824 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5825 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5826 *
5827 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5828 */
5829 static char sslv3_client_hello[] = {
5830 "16" /* ContentType : 0x16 = Hanshake */
5831 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5832 "0079" /* ContentLength : 0x79 bytes after this one */
5833 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5834 "000075" /* HandshakeLength : 0x75 bytes after this one */
5835 "0300" /* Hello Version : 0x0300 = v3 */
5836 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5837 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5838 "00" /* Session ID length : empty (no session ID) */
5839 "004E" /* Cipher Suite Length : 78 bytes after this one */
5840 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5841 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5842 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5843 "000D" "000E" "000F" "0010" /* various bit lengths, */
5844 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5845 "0015" "0016" "0017" "0018"
5846 "0019" "001A" "001B" "002F"
5847 "0030" "0031" "0032" "0033"
5848 "0034" "0035" "0036" "0037"
5849 "0038" "0039" "003A"
5850 "01" /* Compression Length : 0x01 = 1 byte for types */
5851 "00" /* Compression Type : 0x00 = NULL compression */
5852 };
5853
5854 struct tcpcheck_ruleset *rs = NULL;
5855 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5856 struct tcpcheck_rule *chk;
5857 char *errmsg = NULL;
5858 int err_code = 0;
5859
5860 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5861 err_code |= ERR_WARN;
5862
5863 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5864 goto out;
5865
Christopher Faulet811f78c2020-04-01 11:10:27 +02005866 curpx->options2 &= ~PR_O2_CHK_ANY;
5867 curpx->options2 |= PR_O2_TCPCHK_CHK;
5868
5869 free_tcpcheck_vars(&rules->preset_vars);
5870 rules->list = NULL;
5871 rules->flags = 0;
5872
Christopher Faulet61cc8522020-04-20 14:54:42 +02005873 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005874 if (rs)
5875 goto ruleset_found;
5876
Christopher Faulet61cc8522020-04-20 14:54:42 +02005877 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005878 if (rs == NULL) {
5879 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5880 goto error;
5881 }
5882
5883 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5884 1, curpx, &rs->rules, file, line, &errmsg);
5885 if (!chk) {
5886 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5887 goto error;
5888 }
5889 chk->index = 0;
5890 LIST_ADDQ(&rs->rules, &chk->list);
5891
5892 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005893 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005894 "error-status", "L6RSP", "tout-status", "L6TOUT",
5895 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005896 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005897 if (!chk) {
5898 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5899 goto error;
5900 }
5901 chk->index = 1;
5902 LIST_ADDQ(&rs->rules, &chk->list);
5903
Christopher Fauletd7cee712020-04-21 13:45:00 +02005904 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005905
5906 ruleset_found:
5907 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005908 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005909
5910 out:
5911 free(errmsg);
5912 return err_code;
5913
5914 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005915 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005916 err_code |= ERR_ALERT | ERR_FATAL;
5917 goto out;
5918}
5919
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005920/* Parses the "option smtpchk" proxy keyword */
5921int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5922 const char *file, int line)
5923{
5924 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5925
5926 struct tcpcheck_ruleset *rs = NULL;
5927 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5928 struct tcpcheck_rule *chk;
5929 struct tcpcheck_var *var = NULL;
5930 char *cmd = NULL, *errmsg = NULL;
5931 int err_code = 0;
5932
5933 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5934 err_code |= ERR_WARN;
5935
5936 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5937 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005938
5939 curpx->options2 &= ~PR_O2_CHK_ANY;
5940 curpx->options2 |= PR_O2_TCPCHK_CHK;
5941
5942 free_tcpcheck_vars(&rules->preset_vars);
5943 rules->list = NULL;
5944 rules->flags = 0;
5945
5946 cur_arg += 2;
5947 if (*args[cur_arg] && *args[cur_arg+1] &&
5948 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5949 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5950 if (cmd)
5951 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5952 }
5953 else {
5954 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5955 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5956 cmd = strdup("HELO localhost");
5957 }
5958
Christopher Fauletb61caf42020-04-21 10:57:42 +02005959 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005960 if (cmd == NULL || var == NULL) {
5961 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5962 goto error;
5963 }
5964 var->data.type = SMP_T_STR;
5965 var->data.u.str.area = cmd;
5966 var->data.u.str.data = strlen(cmd);
5967 LIST_INIT(&var->list);
5968 LIST_ADDQ(&rules->preset_vars, &var->list);
5969 cmd = NULL;
5970 var = NULL;
5971
Christopher Faulet61cc8522020-04-20 14:54:42 +02005972 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005973 if (rs)
5974 goto ruleset_found;
5975
Christopher Faulet61cc8522020-04-20 14:54:42 +02005976 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005977 if (rs == NULL) {
5978 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5979 goto error;
5980 }
5981
5982 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5983 1, curpx, &rs->rules, file, line, &errmsg);
5984 if (!chk) {
5985 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5986 goto error;
5987 }
5988 chk->index = 0;
5989 LIST_ADDQ(&rs->rules, &chk->list);
5990
5991 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5992 "min-recv", "4",
5993 "error-status", "L7RSP",
5994 "on-error", "%[check.payload(),cut_crlf]",
5995 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005996 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005997 if (!chk) {
5998 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5999 goto error;
6000 }
6001 chk->index = 1;
6002 LIST_ADDQ(&rs->rules, &chk->list);
6003
6004 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6005 "min-recv", "4",
6006 "error-status", "L7STS",
6007 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6008 "status-code", "check.payload(0,3)",
6009 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006010 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006011 if (!chk) {
6012 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6013 goto error;
6014 }
6015 chk->index = 2;
6016 LIST_ADDQ(&rs->rules, &chk->list);
6017
6018 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6019 1, curpx, &rs->rules, file, line, &errmsg);
6020 if (!chk) {
6021 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6022 goto error;
6023 }
6024 chk->index = 3;
6025 LIST_ADDQ(&rs->rules, &chk->list);
6026
6027 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6028 "min-recv", "4",
6029 "error-status", "L7STS",
6030 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6031 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6032 "status-code", "check.payload(0,3)",
6033 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006034 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006035 if (!chk) {
6036 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6037 goto error;
6038 }
6039 chk->index = 4;
6040 LIST_ADDQ(&rs->rules, &chk->list);
6041
Christopher Fauletd7cee712020-04-21 13:45:00 +02006042 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006043
6044 ruleset_found:
6045 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006046 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006047
6048 out:
6049 free(errmsg);
6050 return err_code;
6051
6052 error:
6053 free(cmd);
6054 free(var);
6055 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006056 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006057 err_code |= ERR_ALERT | ERR_FATAL;
6058 goto out;
6059}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006060
Christopher Fauletce355072020-04-02 11:44:39 +02006061/* Parses the "option pgsql-check" proxy keyword */
6062int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6063 const char *file, int line)
6064{
6065 static char pgsql_req[] = {
6066 "%[var(check.plen),htonl,hex]" /* The packet length*/
6067 "00030000" /* the version 3.0 */
6068 "7573657200" /* "user" key */
6069 "%[var(check.username),hex]00" /* the username */
6070 "00"
6071 };
6072
6073 struct tcpcheck_ruleset *rs = NULL;
6074 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6075 struct tcpcheck_rule *chk;
6076 struct tcpcheck_var *var = NULL;
6077 char *user = NULL, *errmsg = NULL;
6078 size_t packetlen = 0;
6079 int err_code = 0;
6080
6081 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6082 err_code |= ERR_WARN;
6083
6084 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6085 goto out;
6086
Christopher Fauletce355072020-04-02 11:44:39 +02006087 curpx->options2 &= ~PR_O2_CHK_ANY;
6088 curpx->options2 |= PR_O2_TCPCHK_CHK;
6089
6090 free_tcpcheck_vars(&rules->preset_vars);
6091 rules->list = NULL;
6092 rules->flags = 0;
6093
6094 cur_arg += 2;
6095 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6096 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6097 file, line, args[0], args[1]);
6098 goto error;
6099 }
6100 if (strcmp(args[cur_arg], "user") == 0) {
6101 packetlen = 15 + strlen(args[cur_arg+1]);
6102 user = strdup(args[cur_arg+1]);
6103
Christopher Fauletb61caf42020-04-21 10:57:42 +02006104 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006105 if (user == NULL || var == NULL) {
6106 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6107 goto error;
6108 }
6109 var->data.type = SMP_T_STR;
6110 var->data.u.str.area = user;
6111 var->data.u.str.data = strlen(user);
6112 LIST_INIT(&var->list);
6113 LIST_ADDQ(&rules->preset_vars, &var->list);
6114 user = NULL;
6115 var = NULL;
6116
Christopher Fauletb61caf42020-04-21 10:57:42 +02006117 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006118 if (var == NULL) {
6119 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6120 goto error;
6121 }
6122 var->data.type = SMP_T_SINT;
6123 var->data.u.sint = packetlen;
6124 LIST_INIT(&var->list);
6125 LIST_ADDQ(&rules->preset_vars, &var->list);
6126 var = NULL;
6127 }
6128 else {
6129 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6130 file, line, args[0], args[1]);
6131 goto error;
6132 }
6133
Christopher Faulet61cc8522020-04-20 14:54:42 +02006134 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006135 if (rs)
6136 goto ruleset_found;
6137
Christopher Faulet61cc8522020-04-20 14:54:42 +02006138 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006139 if (rs == NULL) {
6140 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6141 goto error;
6142 }
6143
6144 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6145 1, curpx, &rs->rules, file, line, &errmsg);
6146 if (!chk) {
6147 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6148 goto error;
6149 }
6150 chk->index = 0;
6151 LIST_ADDQ(&rs->rules, &chk->list);
6152
6153 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6154 1, curpx, &rs->rules, file, line, &errmsg);
6155 if (!chk) {
6156 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6157 goto error;
6158 }
6159 chk->index = 1;
6160 LIST_ADDQ(&rs->rules, &chk->list);
6161
6162 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6163 "min-recv", "5",
6164 "error-status", "L7RSP",
6165 "on-error", "%[check.payload(6,0)]",
6166 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006167 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006168 if (!chk) {
6169 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6170 goto error;
6171 }
6172 chk->index = 2;
6173 LIST_ADDQ(&rs->rules, &chk->list);
6174
6175 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6176 "min-recv", "9",
6177 "error-status", "L7STS",
6178 "on-success", "PostgreSQL server is ok",
6179 "on-error", "PostgreSQL unknown error",
6180 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006181 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006182 if (!chk) {
6183 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6184 goto error;
6185 }
6186 chk->index = 3;
6187 LIST_ADDQ(&rs->rules, &chk->list);
6188
Christopher Fauletd7cee712020-04-21 13:45:00 +02006189 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006190
6191 ruleset_found:
6192 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006193 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006194
6195 out:
6196 free(errmsg);
6197 return err_code;
6198
6199 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006200 free(user);
6201 free(var);
6202 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006203 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006204 err_code |= ERR_ALERT | ERR_FATAL;
6205 goto out;
6206}
6207
6208
6209/* Parses the "option mysql-check" proxy keyword */
6210int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6211 const char *file, int line)
6212{
6213 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6214 * const char mysql40_client_auth_pkt[] = {
6215 * "\x0e\x00\x00" // packet length
6216 * "\x01" // packet number
6217 * "\x00\x00" // client capabilities
6218 * "\x00\x00\x01" // max packet
6219 * "haproxy\x00" // username (null terminated string)
6220 * "\x00" // filler (always 0x00)
6221 * "\x01\x00\x00" // packet length
6222 * "\x00" // packet number
6223 * "\x01" // COM_QUIT command
6224 * };
6225 */
6226 static char mysql40_rsname[] = "*mysql40-check";
6227 static char mysql40_req[] = {
6228 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6229 "0080" /* client capabilities */
6230 "000001" /* max packet */
6231 "%[var(check.username),hex]00" /* the username */
6232 "00" /* filler (always 0x00) */
6233 "010000" /* packet length*/
6234 "00" /* sequence ID */
6235 "01" /* COM_QUIT command */
6236 };
6237
6238 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6239 * const char mysql41_client_auth_pkt[] = {
6240 * "\x0e\x00\x00\" // packet length
6241 * "\x01" // packet number
6242 * "\x00\x00\x00\x00" // client capabilities
6243 * "\x00\x00\x00\x01" // max packet
6244 * "\x21" // character set (UTF-8)
6245 * char[23] // All zeroes
6246 * "haproxy\x00" // username (null terminated string)
6247 * "\x00" // filler (always 0x00)
6248 * "\x01\x00\x00" // packet length
6249 * "\x00" // packet number
6250 * "\x01" // COM_QUIT command
6251 * };
6252 */
6253 static char mysql41_rsname[] = "*mysql41-check";
6254 static char mysql41_req[] = {
6255 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6256 "00820000" /* client capabilities */
6257 "00800001" /* max packet */
6258 "21" /* character set (UTF-8) */
6259 "000000000000000000000000" /* 23 bytes, al zeroes */
6260 "0000000000000000000000"
6261 "%[var(check.username),hex]00" /* the username */
6262 "00" /* filler (always 0x00) */
6263 "010000" /* packet length*/
6264 "00" /* sequence ID */
6265 "01" /* COM_QUIT command */
6266 };
6267
6268 struct tcpcheck_ruleset *rs = NULL;
6269 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6270 struct tcpcheck_rule *chk;
6271 struct tcpcheck_var *var = NULL;
6272 char *mysql_rsname = "*mysql-check";
6273 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6274 int index = 0, err_code = 0;
6275
6276 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6277 err_code |= ERR_WARN;
6278
6279 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6280 goto out;
6281
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006282 curpx->options2 &= ~PR_O2_CHK_ANY;
6283 curpx->options2 |= PR_O2_TCPCHK_CHK;
6284
6285 free_tcpcheck_vars(&rules->preset_vars);
6286 rules->list = NULL;
6287 rules->flags = 0;
6288
6289 cur_arg += 2;
6290 if (*args[cur_arg]) {
6291 char *user;
6292 int packetlen, userlen;
6293
6294 if (strcmp(args[cur_arg], "user") != 0) {
6295 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6296 file, line, args[0], args[1], args[cur_arg]);
6297 goto error;
6298 }
6299
6300 if (*(args[cur_arg+1]) == 0) {
6301 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6302 file, line, args[0], args[1], args[cur_arg]);
6303 goto error;
6304 }
6305
6306 hdr = calloc(4, sizeof(*hdr));
6307 user = strdup(args[cur_arg+1]);
6308 userlen = strlen(args[cur_arg+1]);
6309
6310 if (hdr == NULL || user == NULL) {
6311 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6312 goto error;
6313 }
6314
6315 if (*args[cur_arg+2]) {
6316 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6317 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6318 file, line, args[cur_arg], args[cur_arg+2]);
6319 goto error;
6320 }
6321 packetlen = userlen + 7 + 27;
6322 mysql_req = mysql41_req;
6323 mysql_rsname = mysql41_rsname;
6324 }
6325 else {
6326 packetlen = userlen + 7;
6327 mysql_req = mysql40_req;
6328 mysql_rsname = mysql40_rsname;
6329 }
6330
6331 hdr[0] = (unsigned char)(packetlen & 0xff);
6332 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6333 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6334 hdr[3] = 1;
6335
Christopher Fauletb61caf42020-04-21 10:57:42 +02006336 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006337 if (var == NULL) {
6338 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6339 goto error;
6340 }
6341 var->data.type = SMP_T_STR;
6342 var->data.u.str.area = hdr;
6343 var->data.u.str.data = 4;
6344 LIST_INIT(&var->list);
6345 LIST_ADDQ(&rules->preset_vars, &var->list);
6346 hdr = NULL;
6347 var = NULL;
6348
Christopher Fauletb61caf42020-04-21 10:57:42 +02006349 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006350 if (var == NULL) {
6351 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6352 goto error;
6353 }
6354 var->data.type = SMP_T_STR;
6355 var->data.u.str.area = user;
6356 var->data.u.str.data = strlen(user);
6357 LIST_INIT(&var->list);
6358 LIST_ADDQ(&rules->preset_vars, &var->list);
6359 user = NULL;
6360 var = NULL;
6361 }
6362
Christopher Faulet61cc8522020-04-20 14:54:42 +02006363 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006364 if (rs)
6365 goto ruleset_found;
6366
Christopher Faulet61cc8522020-04-20 14:54:42 +02006367 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006368 if (rs == NULL) {
6369 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6370 goto error;
6371 }
6372
6373 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6374 1, curpx, &rs->rules, file, line, &errmsg);
6375 if (!chk) {
6376 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6377 goto error;
6378 }
6379 chk->index = index++;
6380 LIST_ADDQ(&rs->rules, &chk->list);
6381
6382 if (mysql_req) {
6383 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6384 1, curpx, &rs->rules, file, line, &errmsg);
6385 if (!chk) {
6386 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6387 goto error;
6388 }
6389 chk->index = index++;
6390 LIST_ADDQ(&rs->rules, &chk->list);
6391 }
6392
6393 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006394 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006395 if (!chk) {
6396 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6397 goto error;
6398 }
6399 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6400 chk->index = index++;
6401 LIST_ADDQ(&rs->rules, &chk->list);
6402
6403 if (mysql_req) {
6404 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006405 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006406 if (!chk) {
6407 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6408 goto error;
6409 }
6410 chk->expect.custom = tcpcheck_mysql_expect_ok;
6411 chk->index = index++;
6412 LIST_ADDQ(&rs->rules, &chk->list);
6413 }
6414
Christopher Fauletd7cee712020-04-21 13:45:00 +02006415 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006416
6417 ruleset_found:
6418 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006419 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006420
6421 out:
6422 free(errmsg);
6423 return err_code;
6424
6425 error:
6426 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006427 free(user);
6428 free(var);
6429 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006430 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006431 err_code |= ERR_ALERT | ERR_FATAL;
6432 goto out;
6433}
6434
Christopher Faulet1997eca2020-04-03 23:13:50 +02006435int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6436 const char *file, int line)
6437{
6438 static char *ldap_req = "300C020101600702010304008000";
6439
6440 struct tcpcheck_ruleset *rs = NULL;
6441 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6442 struct tcpcheck_rule *chk;
6443 char *errmsg = NULL;
6444 int err_code = 0;
6445
6446 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6447 err_code |= ERR_WARN;
6448
6449 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6450 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006451
6452 curpx->options2 &= ~PR_O2_CHK_ANY;
6453 curpx->options2 |= PR_O2_TCPCHK_CHK;
6454
6455 free_tcpcheck_vars(&rules->preset_vars);
6456 rules->list = NULL;
6457 rules->flags = 0;
6458
Christopher Faulet61cc8522020-04-20 14:54:42 +02006459 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006460 if (rs)
6461 goto ruleset_found;
6462
Christopher Faulet61cc8522020-04-20 14:54:42 +02006463 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006464 if (rs == NULL) {
6465 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6466 goto error;
6467 }
6468
6469 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6470 1, curpx, &rs->rules, file, line, &errmsg);
6471 if (!chk) {
6472 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6473 goto error;
6474 }
6475 chk->index = 0;
6476 LIST_ADDQ(&rs->rules, &chk->list);
6477
6478 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6479 "min-recv", "14",
6480 "on-error", "Not LDAPv3 protocol",
6481 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006482 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006483 if (!chk) {
6484 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6485 goto error;
6486 }
6487 chk->index = 1;
6488 LIST_ADDQ(&rs->rules, &chk->list);
6489
6490 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006491 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006492 if (!chk) {
6493 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6494 goto error;
6495 }
6496 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6497 chk->index = 2;
6498 LIST_ADDQ(&rs->rules, &chk->list);
6499
Christopher Fauletd7cee712020-04-21 13:45:00 +02006500 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006501
6502 ruleset_found:
6503 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006504 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006505
6506 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006507 free(errmsg);
6508 return err_code;
6509
6510 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006511 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006512 err_code |= ERR_ALERT | ERR_FATAL;
6513 goto out;
6514}
6515
6516int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6517 const char *file, int line)
6518{
6519 struct tcpcheck_ruleset *rs = NULL;
6520 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6521 struct tcpcheck_rule *chk;
6522 char *spop_req = NULL;
6523 char *errmsg = NULL;
6524 int spop_len = 0, err_code = 0;
6525
6526 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6527 err_code |= ERR_WARN;
6528
6529 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6530 goto out;
6531
Christopher Faulet267b01b2020-04-04 10:27:09 +02006532 curpx->options2 &= ~PR_O2_CHK_ANY;
6533 curpx->options2 |= PR_O2_TCPCHK_CHK;
6534
6535 free_tcpcheck_vars(&rules->preset_vars);
6536 rules->list = NULL;
6537 rules->flags = 0;
6538
6539
Christopher Faulet61cc8522020-04-20 14:54:42 +02006540 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006541 if (rs)
6542 goto ruleset_found;
6543
Christopher Faulet61cc8522020-04-20 14:54:42 +02006544 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006545 if (rs == NULL) {
6546 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6547 goto error;
6548 }
6549
6550 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6551 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6552 goto error;
6553 }
6554 chunk_reset(&trash);
6555 dump_binary(&trash, spop_req, spop_len);
6556 trash.area[trash.data] = '\0';
6557
6558 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6559 1, curpx, &rs->rules, file, line, &errmsg);
6560 if (!chk) {
6561 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6562 goto error;
6563 }
6564 chk->index = 0;
6565 LIST_ADDQ(&rs->rules, &chk->list);
6566
6567 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006568 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006569 if (!chk) {
6570 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6571 goto error;
6572 }
6573 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6574 chk->index = 1;
6575 LIST_ADDQ(&rs->rules, &chk->list);
6576
Christopher Fauletd7cee712020-04-21 13:45:00 +02006577 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006578
6579 ruleset_found:
6580 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006581 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006582
6583 out:
6584 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006585 free(errmsg);
6586 return err_code;
6587
6588 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006589 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006590 err_code |= ERR_ALERT | ERR_FATAL;
6591 goto out;
6592}
Christopher Fauletce355072020-04-02 11:44:39 +02006593
Christopher Faulete5870d82020-04-15 11:32:03 +02006594
6595struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6596{
6597 struct tcpcheck_rule *chk = NULL;
6598 struct tcpcheck_http_hdr *hdr = NULL;
6599 char *meth = NULL, *uri = NULL, *vsn = NULL;
6600 char *hdrs, *body;
6601
6602 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6603 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6604 if (hdrs == body)
6605 hdrs = NULL;
6606 if (hdrs) {
6607 *hdrs = '\0';
6608 hdrs +=2;
6609 }
6610 if (body) {
6611 *body = '\0';
6612 body += 4;
6613 }
6614 if (hdrs || body) {
6615 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6616 " Please, consider to use 'http-check send' directive instead.");
6617 }
6618
6619 chk = calloc(1, sizeof(*chk));
6620 if (!chk) {
6621 memprintf(errmsg, "out of memory");
6622 goto error;
6623 }
6624 chk->action = TCPCHK_ACT_SEND;
6625 chk->send.type = TCPCHK_SEND_HTTP;
6626 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6627 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6628 LIST_INIT(&chk->send.http.hdrs);
6629
6630 /* Copy the method, uri and version */
6631 if (*args[cur_arg]) {
6632 if (!*args[cur_arg+1])
6633 uri = args[cur_arg];
6634 else
6635 meth = args[cur_arg];
6636 }
6637 if (*args[cur_arg+1])
6638 uri = args[cur_arg+1];
6639 if (*args[cur_arg+2])
6640 vsn = args[cur_arg+2];
6641
6642 if (meth) {
6643 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6644 chk->send.http.meth.str.area = strdup(meth);
6645 chk->send.http.meth.str.data = strlen(meth);
6646 if (!chk->send.http.meth.str.area) {
6647 memprintf(errmsg, "out of memory");
6648 goto error;
6649 }
6650 }
6651 if (uri) {
6652 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006653 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006654 memprintf(errmsg, "out of memory");
6655 goto error;
6656 }
6657 }
6658 if (vsn) {
6659 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006660 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006661 memprintf(errmsg, "out of memory");
6662 goto error;
6663 }
6664 }
6665
6666 /* Copy the header */
6667 if (hdrs) {
6668 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6669 struct h1m h1m;
6670 int i, ret;
6671
6672 /* Build and parse the request */
6673 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6674
6675 h1m.flags = H1_MF_HDRS_ONLY;
6676 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6677 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6678 &h1m, NULL);
6679 if (ret <= 0) {
6680 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6681 goto error;
6682 }
6683
Christopher Fauletb61caf42020-04-21 10:57:42 +02006684 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006685 hdr = calloc(1, sizeof(*hdr));
6686 if (!hdr) {
6687 memprintf(errmsg, "out of memory");
6688 goto error;
6689 }
6690 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006691 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006692 if (!hdr->name.ptr) {
6693 memprintf(errmsg, "out of memory");
6694 goto error;
6695 }
6696
Christopher Fauletb61caf42020-04-21 10:57:42 +02006697 ist0(tmp_hdrs[i].v);
6698 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 +02006699 goto error;
6700 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6701 }
6702 }
6703
6704 /* Copy the body */
6705 if (body) {
6706 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006707 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006708 memprintf(errmsg, "out of memory");
6709 goto error;
6710 }
6711 }
6712
6713 return chk;
6714
6715 error:
6716 free_tcpcheck_http_hdr(hdr);
6717 free_tcpcheck(chk, 0);
6718 return NULL;
6719}
6720
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006721int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6722 const char *file, int line)
6723{
Christopher Faulete5870d82020-04-15 11:32:03 +02006724 struct tcpcheck_ruleset *rs = NULL;
6725 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6726 struct tcpcheck_rule *chk;
6727 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006728 int err_code = 0;
6729
6730 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6731 err_code |= ERR_WARN;
6732
6733 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6734 goto out;
6735
Christopher Faulete5870d82020-04-15 11:32:03 +02006736 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6737 if (!chk) {
6738 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6739 goto error;
6740 }
6741 if (errmsg) {
6742 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6743 err_code |= ERR_WARN;
6744 free(errmsg);
6745 errmsg = NULL;
6746 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006747
Christopher Faulete5870d82020-04-15 11:32:03 +02006748 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006749 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006750 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006751
Christopher Faulete5870d82020-04-15 11:32:03 +02006752 free_tcpcheck_vars(&rules->preset_vars);
6753 rules->list = NULL;
6754 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006755
Christopher Faulete5870d82020-04-15 11:32:03 +02006756 /* Deduce the ruleset name from the proxy info */
6757 chunk_printf(&trash, "*http-check-%s_%s-%d",
6758 ((curpx == defpx) ? "defaults" : curpx->id),
6759 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006760
Christopher Faulet61cc8522020-04-20 14:54:42 +02006761 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006762 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006763 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006764 if (rs == NULL) {
6765 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6766 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006767 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006768 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006769
Christopher Faulete5870d82020-04-15 11:32:03 +02006770 rules->list = &rs->rules;
6771 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6772 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6773 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6774 rules->list = NULL;
6775 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006776 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006777
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006778 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006779 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006780 return err_code;
6781
6782 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006783 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006784 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006785 err_code |= ERR_ALERT | ERR_FATAL;
6786 goto out;
6787}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006788
Christopher Faulet6f557912020-04-09 15:58:50 +02006789int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6790 const char *file, int line)
6791{
6792 int err_code = 0;
6793
Christopher Faulet6f557912020-04-09 15:58:50 +02006794 curpx->options2 &= ~PR_O2_CHK_ANY;
6795 curpx->options2 |= PR_O2_EXT_CHK;
6796 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6797 goto out;
6798
6799 out:
6800 return err_code;
6801}
6802
Christopher Fauletce8111e2020-04-06 15:04:11 +02006803/* Parse the "addr" server keyword */
6804static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6805 char **errmsg)
6806{
6807 struct sockaddr_storage *sk;
6808 struct protocol *proto;
6809 int port1, port2, err_code = 0;
6810
6811
6812 if (!*args[*cur_arg+1]) {
6813 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6814 goto error;
6815 }
6816
6817 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6818 if (!sk) {
6819 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6820 goto error;
6821 }
6822
6823 proto = protocol_by_family(sk->ss_family);
6824 if (!proto || !proto->connect) {
6825 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6826 args[*cur_arg], args[*cur_arg+1]);
6827 goto error;
6828 }
6829
6830 if (port1 != port2) {
6831 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6832 args[*cur_arg], args[*cur_arg+1]);
6833 goto error;
6834 }
6835
6836 srv->check.addr = srv->agent.addr = *sk;
6837 srv->flags |= SRV_F_CHECKADDR;
6838 srv->flags |= SRV_F_AGENTADDR;
6839
6840 out:
6841 return err_code;
6842
6843 error:
6844 err_code |= ERR_ALERT | ERR_FATAL;
6845 goto out;
6846}
6847
6848
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006849/* Parse the "agent-addr" server keyword */
6850static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6851 char **errmsg)
6852{
6853 int err_code = 0;
6854
6855 if (!*(args[*cur_arg+1])) {
6856 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6857 goto error;
6858 }
6859 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6860 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6861 goto error;
6862 }
6863
6864 out:
6865 return err_code;
6866
6867 error:
6868 err_code |= ERR_ALERT | ERR_FATAL;
6869 goto out;
6870}
6871
6872/* Parse the "agent-check" server keyword */
6873static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6874 char **errmsg)
6875{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006876 struct tcpcheck_ruleset *rs = NULL;
6877 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6878 struct tcpcheck_rule *chk;
6879 int err_code = 0;
6880
6881 if (srv->do_agent)
6882 goto out;
6883
6884 if (!rules) {
6885 rules = calloc(1, sizeof(*rules));
6886 if (!rules) {
6887 memprintf(errmsg, "out of memory.");
6888 goto error;
6889 }
6890 LIST_INIT(&rules->preset_vars);
6891 srv->agent.tcpcheck_rules = rules;
6892 }
6893 rules->list = NULL;
6894 rules->flags = 0;
6895
Christopher Faulet61cc8522020-04-20 14:54:42 +02006896 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006897 if (rs)
6898 goto ruleset_found;
6899
Christopher Faulet61cc8522020-04-20 14:54:42 +02006900 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006901 if (rs == NULL) {
6902 memprintf(errmsg, "out of memory.");
6903 goto error;
6904 }
6905
6906 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6907 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6908 if (!chk) {
6909 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6910 goto error;
6911 }
6912 chk->index = 0;
6913 LIST_ADDQ(&rs->rules, &chk->list);
6914
6915 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006916 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6917 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006918 if (!chk) {
6919 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6920 goto error;
6921 }
6922 chk->expect.custom = tcpcheck_agent_expect_reply;
6923 chk->index = 1;
6924 LIST_ADDQ(&rs->rules, &chk->list);
6925
Christopher Fauletd7cee712020-04-21 13:45:00 +02006926 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006927
6928 ruleset_found:
6929 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006930 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006931 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006932
6933 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006934 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006935
6936 error:
6937 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006938 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006939 err_code |= ERR_ALERT | ERR_FATAL;
6940 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006941}
6942
6943/* Parse the "agent-inter" server keyword */
6944static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6945 char **errmsg)
6946{
6947 const char *err = NULL;
6948 unsigned int delay;
6949 int err_code = 0;
6950
6951 if (!*(args[*cur_arg+1])) {
6952 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6953 goto error;
6954 }
6955
6956 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6957 if (err == PARSE_TIME_OVER) {
6958 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6959 args[*cur_arg+1], args[*cur_arg], srv->id);
6960 goto error;
6961 }
6962 else if (err == PARSE_TIME_UNDER) {
6963 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6964 args[*cur_arg+1], args[*cur_arg], srv->id);
6965 goto error;
6966 }
6967 else if (err) {
6968 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6969 *err, srv->id);
6970 goto error;
6971 }
6972 if (delay <= 0) {
6973 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6974 delay, args[*cur_arg], srv->id);
6975 goto error;
6976 }
6977 srv->agent.inter = delay;
6978
6979 out:
6980 return err_code;
6981
6982 error:
6983 err_code |= ERR_ALERT | ERR_FATAL;
6984 goto out;
6985}
6986
6987/* Parse the "agent-port" server keyword */
6988static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6989 char **errmsg)
6990{
6991 int err_code = 0;
6992
6993 if (!*(args[*cur_arg+1])) {
6994 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6995 goto error;
6996 }
6997
6998 global.maxsock++;
6999 srv->agent.port = atol(args[*cur_arg+1]);
7000
7001 out:
7002 return err_code;
7003
7004 error:
7005 err_code |= ERR_ALERT | ERR_FATAL;
7006 goto out;
7007}
7008
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007009int set_srv_agent_send(struct server *srv, const char *send)
7010{
7011 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7012 struct tcpcheck_var *var = NULL;
7013 char *str;
7014
7015 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007016 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007017 if (str == NULL || var == NULL)
7018 goto error;
7019
7020 free_tcpcheck_vars(&rules->preset_vars);
7021
7022 var->data.type = SMP_T_STR;
7023 var->data.u.str.area = str;
7024 var->data.u.str.data = strlen(str);
7025 LIST_INIT(&var->list);
7026 LIST_ADDQ(&rules->preset_vars, &var->list);
7027
7028 return 1;
7029
7030 error:
7031 free(str);
7032 free(var);
7033 return 0;
7034}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007035
7036/* Parse the "agent-send" server keyword */
7037static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7038 char **errmsg)
7039{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007040 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007041 int err_code = 0;
7042
7043 if (!*(args[*cur_arg+1])) {
7044 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7045 goto error;
7046 }
7047
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007048 if (!rules) {
7049 rules = calloc(1, sizeof(*rules));
7050 if (!rules) {
7051 memprintf(errmsg, "out of memory.");
7052 goto error;
7053 }
7054 LIST_INIT(&rules->preset_vars);
7055 srv->agent.tcpcheck_rules = rules;
7056 }
7057
7058 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007059 memprintf(errmsg, "out of memory.");
7060 goto error;
7061 }
7062
7063 out:
7064 return err_code;
7065
7066 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007067 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007068 err_code |= ERR_ALERT | ERR_FATAL;
7069 goto out;
7070}
7071
7072/* Parse the "no-agent-send" server keyword */
7073static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7074 char **errmsg)
7075{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007076 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007077 return 0;
7078}
7079
Christopher Fauletce8111e2020-04-06 15:04:11 +02007080/* Parse the "check" server keyword */
7081static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7082 char **errmsg)
7083{
7084 srv->do_check = 1;
7085 return 0;
7086}
7087
7088/* Parse the "check-send-proxy" server keyword */
7089static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7090 char **errmsg)
7091{
7092 srv->check.send_proxy = 1;
7093 return 0;
7094}
7095
7096/* Parse the "check-via-socks4" server keyword */
7097static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7098 char **errmsg)
7099{
7100 srv->check.via_socks4 = 1;
7101 return 0;
7102}
7103
7104/* Parse the "no-check" server keyword */
7105static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7106 char **errmsg)
7107{
7108 deinit_srv_check(srv);
7109 return 0;
7110}
7111
7112/* Parse the "no-check-send-proxy" server keyword */
7113static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7114 char **errmsg)
7115{
7116 srv->check.send_proxy = 0;
7117 return 0;
7118}
7119
7120/* Parse the "rise" server keyword */
7121static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7122 char **errmsg)
7123{
7124 int err_code = 0;
7125
7126 if (!*args[*cur_arg + 1]) {
7127 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7128 goto error;
7129 }
7130
7131 srv->check.rise = atol(args[*cur_arg+1]);
7132 if (srv->check.rise <= 0) {
7133 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7134 goto error;
7135 }
7136
7137 if (srv->check.health)
7138 srv->check.health = srv->check.rise;
7139
7140 out:
7141 return err_code;
7142
7143 error:
7144 deinit_srv_agent_check(srv);
7145 err_code |= ERR_ALERT | ERR_FATAL;
7146 goto out;
7147 return 0;
7148}
7149
7150/* Parse the "fall" server keyword */
7151static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7152 char **errmsg)
7153{
7154 int err_code = 0;
7155
7156 if (!*args[*cur_arg + 1]) {
7157 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7158 goto error;
7159 }
7160
7161 srv->check.fall = atol(args[*cur_arg+1]);
7162 if (srv->check.fall <= 0) {
7163 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7164 goto error;
7165 }
7166
7167 out:
7168 return err_code;
7169
7170 error:
7171 deinit_srv_agent_check(srv);
7172 err_code |= ERR_ALERT | ERR_FATAL;
7173 goto out;
7174 return 0;
7175}
7176
7177/* Parse the "inter" server keyword */
7178static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7179 char **errmsg)
7180{
7181 const char *err = NULL;
7182 unsigned int delay;
7183 int err_code = 0;
7184
7185 if (!*(args[*cur_arg+1])) {
7186 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7187 goto error;
7188 }
7189
7190 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7191 if (err == PARSE_TIME_OVER) {
7192 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7193 args[*cur_arg+1], args[*cur_arg], srv->id);
7194 goto error;
7195 }
7196 else if (err == PARSE_TIME_UNDER) {
7197 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7198 args[*cur_arg+1], args[*cur_arg], srv->id);
7199 goto error;
7200 }
7201 else if (err) {
7202 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7203 *err, srv->id);
7204 goto error;
7205 }
7206 if (delay <= 0) {
7207 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7208 delay, args[*cur_arg], srv->id);
7209 goto error;
7210 }
7211 srv->check.inter = delay;
7212
7213 out:
7214 return err_code;
7215
7216 error:
7217 err_code |= ERR_ALERT | ERR_FATAL;
7218 goto out;
7219}
7220
7221
7222/* Parse the "fastinter" server keyword */
7223static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7224 char **errmsg)
7225{
7226 const char *err = NULL;
7227 unsigned int delay;
7228 int err_code = 0;
7229
7230 if (!*(args[*cur_arg+1])) {
7231 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7232 goto error;
7233 }
7234
7235 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7236 if (err == PARSE_TIME_OVER) {
7237 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7238 args[*cur_arg+1], args[*cur_arg], srv->id);
7239 goto error;
7240 }
7241 else if (err == PARSE_TIME_UNDER) {
7242 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7243 args[*cur_arg+1], args[*cur_arg], srv->id);
7244 goto error;
7245 }
7246 else if (err) {
7247 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7248 *err, srv->id);
7249 goto error;
7250 }
7251 if (delay <= 0) {
7252 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7253 delay, args[*cur_arg], srv->id);
7254 goto error;
7255 }
7256 srv->check.fastinter = delay;
7257
7258 out:
7259 return err_code;
7260
7261 error:
7262 err_code |= ERR_ALERT | ERR_FATAL;
7263 goto out;
7264}
7265
7266
7267/* Parse the "downinter" server keyword */
7268static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7269 char **errmsg)
7270{
7271 const char *err = NULL;
7272 unsigned int delay;
7273 int err_code = 0;
7274
7275 if (!*(args[*cur_arg+1])) {
7276 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7277 goto error;
7278 }
7279
7280 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7281 if (err == PARSE_TIME_OVER) {
7282 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7283 args[*cur_arg+1], args[*cur_arg], srv->id);
7284 goto error;
7285 }
7286 else if (err == PARSE_TIME_UNDER) {
7287 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7288 args[*cur_arg+1], args[*cur_arg], srv->id);
7289 goto error;
7290 }
7291 else if (err) {
7292 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7293 *err, srv->id);
7294 goto error;
7295 }
7296 if (delay <= 0) {
7297 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7298 delay, args[*cur_arg], srv->id);
7299 goto error;
7300 }
7301 srv->check.downinter = delay;
7302
7303 out:
7304 return err_code;
7305
7306 error:
7307 err_code |= ERR_ALERT | ERR_FATAL;
7308 goto out;
7309}
7310
7311/* Parse the "port" server keyword */
7312static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7313 char **errmsg)
7314{
7315 int err_code = 0;
7316
7317 if (!*(args[*cur_arg+1])) {
7318 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7319 goto error;
7320 }
7321
7322 global.maxsock++;
7323 srv->check.port = atol(args[*cur_arg+1]);
7324 srv->flags |= SRV_F_CHECKPORT;
7325
7326 out:
7327 return err_code;
7328
7329 error:
7330 err_code |= ERR_ALERT | ERR_FATAL;
7331 goto out;
7332}
7333
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007334static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007335 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7336 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7337 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007338 { 0, NULL, NULL },
7339}};
7340
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007341static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007342 { "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 +02007343 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7344 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7345 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7346 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7347 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007348 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
7349 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7350 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007351 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007352 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7353 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7354 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7355 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7356 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7357 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7358 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7359 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007360 { NULL, NULL, 0 },
7361}};
7362
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007363INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007364INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007365
Willy Tarreaubd741542010-03-16 18:46:54 +01007366/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007367 * Local variables:
7368 * c-indent-level: 8
7369 * c-basic-offset: 8
7370 * End:
7371 */