blob: 0b934b663708294c528fc4d7c14591678594ca31 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
1205 if (!last_read && b_data(&check->bi) < offset+4) {
1206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
1356
1357 wait_more_data:
1358 ret = TCPCHK_EVAL_WAIT;
1359 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001360}
1361
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1363 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1364 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001365 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001366static 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 +02001367{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1369 enum healthcheck_status status;
1370 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001371 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001372 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Willy Tarreaubaaee002006-06-26 02:48:02 +02001374
Christopher Faulet61cc8522020-04-20 14:54:42 +02001375 memcpy(&framesz, b_head(&check->bi), 4);
1376 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001377
Christopher Faulet61cc8522020-04-20 14:54:42 +02001378 if (!last_read && b_data(&check->bi) < (4+framesz))
1379 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001380
Christopher Faulet61cc8522020-04-20 14:54:42 +02001381 memset(b_orig(&trash), 0, b_size(&trash));
1382 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1383 status = HCHK_STATUS_L7RSP;
1384 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1385 goto error;
1386 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001387
Christopher Faulet61cc8522020-04-20 14:54:42 +02001388 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 out:
1391 free_trash_chunk(msg);
1392 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001393
Christopher Faulet61cc8522020-04-20 14:54:42 +02001394 error:
1395 ret = TCPCHK_EVAL_STOP;
1396 msg = alloc_trash_chunk();
1397 if (msg)
1398 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1399 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1400 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001401
Christopher Faulet61cc8522020-04-20 14:54:42 +02001402 wait_more_data:
1403 ret = TCPCHK_EVAL_WAIT;
1404 goto out;
1405}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001406
Christopher Faulet61cc8522020-04-20 14:54:42 +02001407/* Custom tcp-check expect function to parse and validate the agent-check
1408 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1409 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1410 */
1411static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1412{
1413 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1414 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1415 const char *hs = NULL; /* health status */
1416 const char *as = NULL; /* admin status */
1417 const char *ps = NULL; /* performance status */
1418 const char *cs = NULL; /* maxconn */
1419 const char *err = NULL; /* first error to report */
1420 const char *wrn = NULL; /* first warning to report */
1421 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001422
Christopher Faulet61cc8522020-04-20 14:54:42 +02001423 /* We're getting an agent check response. The agent could
1424 * have been disabled in the mean time with a long check
1425 * still pending. It is important that we ignore the whole
1426 * response.
1427 */
1428 if (!(check->state & CHK_ST_ENABLED))
1429 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001430
Christopher Faulet61cc8522020-04-20 14:54:42 +02001431 /* The agent supports strings made of a single line ended by the
1432 * first CR ('\r') or LF ('\n'). This line is composed of words
1433 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1434 * line may optionally contained a description of a state change
1435 * after a sharp ('#'), which is only considered if a health state
1436 * is announced.
1437 *
1438 * Words may be composed of :
1439 * - a numeric weight suffixed by the percent character ('%').
1440 * - a health status among "up", "down", "stopped", and "fail".
1441 * - an admin status among "ready", "drain", "maint".
1442 *
1443 * These words may appear in any order. If multiple words of the
1444 * same category appear, the last one wins.
1445 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 p = b_head(&check->bi);
1448 while (*p && *p != '\n' && *p != '\r')
1449 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001450
Christopher Faulet61cc8522020-04-20 14:54:42 +02001451 if (!*p) {
1452 if (!last_read)
1453 goto wait_more_data;
1454
1455 /* at least inform the admin that the agent is mis-behaving */
1456 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1457 goto out;
1458 }
1459
1460 *p = 0;
1461 cmd = b_head(&check->bi);
1462
1463 while (*cmd) {
1464 /* look for next word */
1465 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1466 cmd++;
1467 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001468 }
1469
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 if (*cmd == '#') {
1471 /* this is the beginning of a health status description,
1472 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001473 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001474 cmd++;
1475 while (*cmd == '\t' || *cmd == ' ')
1476 cmd++;
1477 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001478 }
1479
Christopher Faulet61cc8522020-04-20 14:54:42 +02001480 /* find the end of the word so that we have a null-terminated
1481 * word between <cmd> and <p>.
1482 */
1483 p = cmd + 1;
1484 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1485 p++;
1486 if (*p)
1487 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001488
Christopher Faulet61cc8522020-04-20 14:54:42 +02001489 /* first, health statuses */
1490 if (strcasecmp(cmd, "up") == 0) {
1491 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1492 status = HCHK_STATUS_L7OKD;
1493 hs = cmd;
1494 }
1495 else if (strcasecmp(cmd, "down") == 0) {
1496 check->server->check.health = 0;
1497 status = HCHK_STATUS_L7STS;
1498 hs = cmd;
1499 }
1500 else if (strcasecmp(cmd, "stopped") == 0) {
1501 check->server->check.health = 0;
1502 status = HCHK_STATUS_L7STS;
1503 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001504 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001505 else if (strcasecmp(cmd, "fail") == 0) {
1506 check->server->check.health = 0;
1507 status = HCHK_STATUS_L7STS;
1508 hs = cmd;
1509 }
1510 /* admin statuses */
1511 else if (strcasecmp(cmd, "ready") == 0) {
1512 as = cmd;
1513 }
1514 else if (strcasecmp(cmd, "drain") == 0) {
1515 as = cmd;
1516 }
1517 else if (strcasecmp(cmd, "maint") == 0) {
1518 as = cmd;
1519 }
1520 /* try to parse a weight here and keep the last one */
1521 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1522 ps = cmd;
1523 }
1524 /* try to parse a maxconn here */
1525 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1526 cs = cmd;
1527 }
1528 else {
1529 /* keep a copy of the first error */
1530 if (!err)
1531 err = cmd;
1532 }
1533 /* skip to next word */
1534 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001535 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001536 /* here, cmd points either to \0 or to the beginning of a
1537 * description. Skip possible leading spaces.
1538 */
1539 while (*cmd == ' ' || *cmd == '\n')
1540 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001541
Christopher Faulet61cc8522020-04-20 14:54:42 +02001542 /* First, update the admin status so that we avoid sending other
1543 * possibly useless warnings and can also update the health if
1544 * present after going back up.
1545 */
1546 if (as) {
1547 if (strcasecmp(as, "drain") == 0)
1548 srv_adm_set_drain(check->server);
1549 else if (strcasecmp(as, "maint") == 0)
1550 srv_adm_set_maint(check->server);
1551 else
1552 srv_adm_set_ready(check->server);
1553 }
Simon Horman98637e52014-06-20 12:30:16 +09001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 /* now change weights */
1556 if (ps) {
1557 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001558
Christopher Faulet61cc8522020-04-20 14:54:42 +02001559 msg = server_parse_weight_change_request(check->server, ps);
1560 if (!wrn || !*wrn)
1561 wrn = msg;
1562 }
Simon Horman98637e52014-06-20 12:30:16 +09001563
Christopher Faulet61cc8522020-04-20 14:54:42 +02001564 if (cs) {
1565 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001566
Christopher Faulet61cc8522020-04-20 14:54:42 +02001567 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001568
Christopher Faulet61cc8522020-04-20 14:54:42 +02001569 msg = server_parse_maxconn_change_request(check->server, cs);
1570 if (!wrn || !*wrn)
1571 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001572 }
1573
Christopher Faulet61cc8522020-04-20 14:54:42 +02001574 /* and finally health status */
1575 if (hs) {
1576 /* We'll report some of the warnings and errors we have
1577 * here. Down reports are critical, we leave them untouched.
1578 * Lack of report, or report of 'UP' leaves the room for
1579 * ERR first, then WARN.
1580 */
1581 const char *msg = cmd;
1582 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001583
Christopher Faulet61cc8522020-04-20 14:54:42 +02001584 if (!*msg || status == HCHK_STATUS_L7OKD) {
1585 if (err && *err)
1586 msg = err;
1587 else if (wrn && *wrn)
1588 msg = wrn;
1589 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001590
Christopher Faulet61cc8522020-04-20 14:54:42 +02001591 t = get_trash_chunk();
1592 chunk_printf(t, "via agent : %s%s%s%s",
1593 hs, *msg ? " (" : "",
1594 msg, *msg ? ")" : "");
1595 set_server_check_status(check, status, t->area);
1596 }
1597 else if (err && *err) {
1598 /* No status change but we'd like to report something odd.
1599 * Just report the current state and copy the message.
1600 */
1601 chunk_printf(&trash, "agent reports an error : %s", err);
1602 set_server_check_status(check, status/*check->status*/, trash.area);
1603 }
1604 else if (wrn && *wrn) {
1605 /* No status change but we'd like to report something odd.
1606 * Just report the current state and copy the message.
1607 */
1608 chunk_printf(&trash, "agent warns : %s", wrn);
1609 set_server_check_status(check, status/*check->status*/, trash.area);
1610 }
1611 else
1612 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001613
Christopher Faulet61cc8522020-04-20 14:54:42 +02001614 out:
1615 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001616
Christopher Faulet61cc8522020-04-20 14:54:42 +02001617 wait_more_data:
1618 ret = TCPCHK_EVAL_WAIT;
1619 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001620}
1621
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1623 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1624 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001625 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001626static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001627{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001628 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1629 struct tcpcheck_connect *connect = &rule->connect;
1630 struct proxy *proxy = check->proxy;
1631 struct server *s = check->server;
1632 struct task *t = check->task;
1633 struct conn_stream *cs;
1634 struct connection *conn = NULL;
1635 struct protocol *proto;
1636 struct xprt_ops *xprt;
1637 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001638
Christopher Faulet61cc8522020-04-20 14:54:42 +02001639 /* For a connect action we'll create a new connection. We may also have
1640 * to kill a previous one. But we don't want to leave *without* a
1641 * connection if we came here from the connection layer, hence with a
1642 * connection. Thus we'll proceed in the following order :
1643 * 1: close but not release previous connection (handled by the caller)
1644 * 2: try to get a new connection
1645 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001646 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001647
Christopher Faulet61cc8522020-04-20 14:54:42 +02001648 /* 2- prepare new connection */
1649 cs = cs_new(NULL);
1650 if (!cs) {
1651 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1652 tcpcheck_get_step_id(check, rule));
1653 if (rule->comment)
1654 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1655 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1656 ret = TCPCHK_EVAL_STOP;
1657 goto out;
1658 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001659
Christopher Faulet61cc8522020-04-20 14:54:42 +02001660 /* 3- release and replace the old one on success */
1661 if (check->cs) {
1662 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001663 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1664 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001665
1666 /* We may have been scheduled to run, and the I/O handler
1667 * expects to have a cs, so remove the tasklet
1668 */
1669 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1670 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001671 }
1672
Christopher Faulet61cc8522020-04-20 14:54:42 +02001673 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 check->cs = cs;
1676 conn = cs->conn;
1677 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001678
Christopher Faulet61cc8522020-04-20 14:54:42 +02001679 /* Maybe there were an older connection we were waiting on */
1680 check->wait_list.events = 0;
1681 conn->target = s ? &s->obj_type : &proxy->obj_type;
1682
1683 /* no client address */
1684 if (!sockaddr_alloc(&conn->dst)) {
1685 status = SF_ERR_RESOURCE;
1686 goto fail_check;
1687 }
1688
1689 /* connect to the connect rule addr if specified, otherwise the check
1690 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001691 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001692 *conn->dst = (is_addr(&connect->addr)
1693 ? connect->addr
1694 : (is_addr(&check->addr) ? check->addr : s->addr));
1695 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001696
Christopher Faulet61cc8522020-04-20 14:54:42 +02001697 port = 0;
1698 if (!port && connect->port)
1699 port = connect->port;
1700 if (!port && connect->port_expr) {
1701 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001702
Christopher Faulet61cc8522020-04-20 14:54:42 +02001703 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1704 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1705 connect->port_expr, SMP_T_SINT);
1706 if (smp)
1707 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001708 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001709 if (!port && is_inet_addr(&connect->addr))
1710 port = get_host_port(&connect->addr);
1711 if (!port && check->port)
1712 port = check->port;
1713 if (!port && is_inet_addr(&check->addr))
1714 port = get_host_port(&check->addr);
1715 if (!port)
1716 port = s->svc_port;
1717 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 xprt = ((connect->options & TCPCHK_OPT_SSL)
1720 ? xprt_get(XPRT_SSL)
1721 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001722
Christopher Faulet61cc8522020-04-20 14:54:42 +02001723 conn_prepare(conn, proto, xprt);
1724 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001725
Christopher Faulet61cc8522020-04-20 14:54:42 +02001726 status = SF_ERR_INTERNAL;
1727 if (proto && proto->connect) {
1728 struct tcpcheck_rule *next;
1729 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001730
Christopher Faulet61cc8522020-04-20 14:54:42 +02001731 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1732 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001733
Christopher Faulet61cc8522020-04-20 14:54:42 +02001734 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1735 if (!next || next->action != TCPCHK_ACT_EXPECT)
1736 flags |= CONNECT_DELACK_ALWAYS;
1737 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001738 }
1739
Christopher Faulet61cc8522020-04-20 14:54:42 +02001740 if (status != SF_ERR_NONE)
1741 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001742
Christopher Faulet61cc8522020-04-20 14:54:42 +02001743 conn->flags |= CO_FL_PRIVATE;
1744 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001745
Christopher Faulet61cc8522020-04-20 14:54:42 +02001746 /* The mux may be initialized now if there isn't server attached to the
1747 * check (email alerts) or if there is a mux proto specified or if there
1748 * is no alpn.
1749 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001750 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1751 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001752 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001753
Christopher Faulet61cc8522020-04-20 14:54:42 +02001754 if (connect->mux_proto)
1755 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001756 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001757 mux_ops = check->mux_proto->mux;
1758 else {
1759 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1760 ? PROTO_MODE_HTTP
1761 : PROTO_MODE_TCP);
1762
1763 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001764 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001765 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1766 status = SF_ERR_INTERNAL;
1767 goto fail_check;
1768 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001769 }
1770
Christopher Faulet61cc8522020-04-20 14:54:42 +02001771#ifdef USE_OPENSSL
1772 if (connect->sni)
1773 ssl_sock_set_servername(conn, connect->sni);
1774 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1775 ssl_sock_set_servername(conn, s->check.sni);
1776
1777 if (connect->alpn)
1778 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1779 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1780 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1781#endif
1782 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1783 conn->send_proxy_ofs = 1;
1784 conn->flags |= CO_FL_SOCKS4;
1785 }
1786 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1787 conn->send_proxy_ofs = 1;
1788 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001789 }
1790
Christopher Faulet61cc8522020-04-20 14:54:42 +02001791 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1792 conn->send_proxy_ofs = 1;
1793 conn->flags |= CO_FL_SEND_PROXY;
1794 }
1795 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1796 conn->send_proxy_ofs = 1;
1797 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001798 }
1799
Christopher Faulet61cc8522020-04-20 14:54:42 +02001800 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1801 /* Some servers don't like reset on close */
1802 fdtab[cs->conn->handle.fd].linger_risk = 0;
1803 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001804
Christopher Faulet61cc8522020-04-20 14:54:42 +02001805 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1806 if (xprt_add_hs(conn) < 0)
1807 status = SF_ERR_RESOURCE;
1808 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001809
Christopher Faulet61cc8522020-04-20 14:54:42 +02001810 fail_check:
1811 /* It can return one of :
1812 * - SF_ERR_NONE if everything's OK
1813 * - SF_ERR_SRVTO if there are no more servers
1814 * - SF_ERR_SRVCL if the connection was refused by the server
1815 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1816 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1817 * - SF_ERR_INTERNAL for any other purely internal errors
1818 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1819 * Note that we try to prevent the network stack from sending the ACK during the
1820 * connect() when a pure TCP check is used (without PROXY protocol).
1821 */
1822 switch (status) {
1823 case SF_ERR_NONE:
1824 /* we allow up to min(inter, timeout.connect) for a connection
1825 * to establish but only when timeout.check is set as it may be
1826 * to short for a full check otherwise
1827 */
1828 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001829
Christopher Faulet61cc8522020-04-20 14:54:42 +02001830 if (proxy->timeout.check && proxy->timeout.connect) {
1831 int t_con = tick_add(now_ms, proxy->timeout.connect);
1832 t->expire = tick_first(t->expire, t_con);
1833 }
1834 break;
1835 case SF_ERR_SRVTO: /* ETIMEDOUT */
1836 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1837 case SF_ERR_PRXCOND:
1838 case SF_ERR_RESOURCE:
1839 case SF_ERR_INTERNAL:
1840 chk_report_conn_err(check, errno, 0);
1841 ret = TCPCHK_EVAL_STOP;
1842 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001843 }
1844
Christopher Faulet61cc8522020-04-20 14:54:42 +02001845 /* don't do anything until the connection is established */
1846 if (conn->flags & CO_FL_WAIT_XPRT) {
1847 ret = TCPCHK_EVAL_WAIT;
1848 goto out;
1849 }
1850
1851 out:
1852 if (conn && check->result == CHK_RES_FAILED)
1853 conn->flags |= CO_FL_ERROR;
1854 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001855}
1856
Christopher Faulet61cc8522020-04-20 14:54:42 +02001857/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1858 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1859 * TCPCHK_EVAL_STOP if an error occurred.
1860 */
1861static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001862{
1863 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001864 struct tcpcheck_send *send = &rule->send;
1865 struct conn_stream *cs = check->cs;
1866 struct connection *conn = cs_conn(cs);
1867 struct buffer *tmp = NULL;
1868 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001869
Christopher Faulet61cc8522020-04-20 14:54:42 +02001870 /* reset the read & write buffer */
1871 b_reset(&check->bi);
1872 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001873
Christopher Faulet61cc8522020-04-20 14:54:42 +02001874 switch (send->type) {
1875 case TCPCHK_SEND_STRING:
1876 case TCPCHK_SEND_BINARY:
1877 if (istlen(send->data) >= b_size(&check->bo)) {
1878 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1879 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1880 tcpcheck_get_step_id(check, rule));
1881 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1882 ret = TCPCHK_EVAL_STOP;
1883 goto out;
1884 }
1885 b_putist(&check->bo, send->data);
1886 break;
1887 case TCPCHK_SEND_STRING_LF:
1888 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1889 if (!b_data(&check->bo))
1890 goto out;
1891 break;
1892 case TCPCHK_SEND_BINARY_LF:
1893 tmp = alloc_trash_chunk();
1894 if (!tmp)
1895 goto error_lf;
1896 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1897 if (!b_data(tmp))
1898 goto out;
1899 tmp->area[tmp->data] = '\0';
1900 b_set_data(&check->bo, b_size(&check->bo));
1901 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1902 goto error_lf;
1903 break;
1904 case TCPCHK_SEND_HTTP: {
1905 struct htx_sl *sl;
1906 struct ist meth, uri, vsn, clen, body;
1907 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001908
Christopher Faulet61cc8522020-04-20 14:54:42 +02001909 tmp = alloc_trash_chunk();
1910 if (!tmp)
1911 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001912
Christopher Faulet61cc8522020-04-20 14:54:42 +02001913 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1914 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1915 : http_known_methods[send->http.meth.meth]);
1916 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1917 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001918
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001919 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1920 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001921 slflags |= HTX_SL_F_VER_11;
1922 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1923 if (!isttest(send->http.body))
1924 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001925
Christopher Faulet61cc8522020-04-20 14:54:42 +02001926 htx = htx_from_buf(&check->bo);
1927 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1928 if (!sl)
1929 goto error_htx;
1930 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001931
Christopher Faulet61cc8522020-04-20 14:54:42 +02001932 body = send->http.body; // TODO: handle body_fmt
1933 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001934
Christopher Faulet61cc8522020-04-20 14:54:42 +02001935 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1936 !htx_add_header(htx, ist("Content-length"), clen))
1937 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001938
Christopher Faulet61cc8522020-04-20 14:54:42 +02001939 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1940 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001941
Christopher Faulet61cc8522020-04-20 14:54:42 +02001942 list_for_each_entry(hdr, &send->http.hdrs, list) {
1943 chunk_reset(tmp);
1944 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1945 if (!b_data(tmp))
1946 continue;
1947 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1948 goto error_htx;
1949 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001950
Christopher Faulet61cc8522020-04-20 14:54:42 +02001951 }
1952 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1953 chunk_reset(tmp);
1954 httpchk_build_status_header(check->server, tmp);
1955 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1956 goto error_htx;
1957 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001958
Christopher Faulet61cc8522020-04-20 14:54:42 +02001959 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1960 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1961 !htx_add_endof(htx, HTX_BLK_EOM))
1962 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001963
Christopher Faulet61cc8522020-04-20 14:54:42 +02001964 htx_to_buf(htx, &check->bo);
1965 break;
1966 }
1967 case TCPCHK_SEND_UNDEF:
1968 /* Should never happen. */
1969 ret = TCPCHK_EVAL_STOP;
1970 goto out;
1971 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001972
Christopher Faulet6d471212020-04-22 11:09:25 +02001973
1974 if (conn->mux->snd_buf(cs, &check->bo,
1975 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001976 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001977 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001978 goto out;
1979 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001980 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001981 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001982 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1983 ret = TCPCHK_EVAL_WAIT;
1984 goto out;
1985 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001986
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 out:
1988 free_trash_chunk(tmp);
1989 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001990
Christopher Faulet61cc8522020-04-20 14:54:42 +02001991 error_htx:
1992 if (htx) {
1993 htx_reset(htx);
1994 htx_to_buf(htx, &check->bo);
1995 }
1996 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1997 tcpcheck_get_step_id(check, rule));
1998 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1999 ret = TCPCHK_EVAL_STOP;
2000 goto out;
2001
2002 error_lf:
2003 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2004 tcpcheck_get_step_id(check, rule));
2005 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2006 ret = TCPCHK_EVAL_STOP;
2007 goto out;
2008
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002009}
2010
Christopher Faulet61cc8522020-04-20 14:54:42 +02002011/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2012 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2013 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2014 * TCPCHK_EVAL_STOP if an error occurred.
2015 */
2016static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002017{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002018 struct conn_stream *cs = check->cs;
2019 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002020 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021 size_t max, read, cur_read = 0;
2022 int is_empty;
2023 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002024
Christopher Faulet61cc8522020-04-20 14:54:42 +02002025 if (check->wait_list.events & SUB_RETRY_RECV)
2026 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002027
Christopher Faulet61cc8522020-04-20 14:54:42 +02002028 if (cs->flags & CS_FL_EOS)
2029 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002032
Christopher Faulet61cc8522020-04-20 14:54:42 +02002033 /* prepare to detect if the mux needs more room */
2034 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002035
Christopher Faulet61cc8522020-04-20 14:54:42 +02002036 while ((cs->flags & CS_FL_RCV_MORE) ||
2037 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2038 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2039 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2040 cur_read += read;
2041 if (!read ||
2042 (cs->flags & CS_FL_WANT_ROOM) ||
2043 (--read_poll <= 0) ||
2044 (read < max && read >= global.tune.recv_enough))
2045 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002046 }
2047
Christopher Faulet61cc8522020-04-20 14:54:42 +02002048 end_recv:
2049 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2050 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2051 /* Report network errors only if we got no other data. Otherwise
2052 * we'll let the upper layers decide whether the response is OK
2053 * or not. It is very common that an RST sent by the server is
2054 * reported as an error just after the last data chunk.
2055 */
2056 goto stop;
2057 }
2058 if (!cur_read) {
2059 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2060 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2061 goto wait_more_data;
2062 }
2063 if (is_empty) {
2064 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2065 tcpcheck_get_step_id(check, rule));
2066 if (rule->comment)
2067 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2068 set_server_check_status(check, rule->expect.err_status, trash.area);
2069 goto stop;
2070 }
2071 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002072
2073 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002074 return ret;
2075
Christopher Faulet61cc8522020-04-20 14:54:42 +02002076 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002077 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002078 goto out;
2079
2080 wait_more_data:
2081 ret = TCPCHK_EVAL_WAIT;
2082 goto out;
2083}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002084
Christopher Faulet61cc8522020-04-20 14:54:42 +02002085/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2086 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2087 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2088 * error occurred.
2089 */
2090static 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 +02002091{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002092 struct htx *htx = htxbuf(&check->bi);
2093 struct htx_sl *sl;
2094 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002095 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002096 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002097 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002099 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002100 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002101
Christopher Faulet61cc8522020-04-20 14:54:42 +02002102 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002103
Christopher Faulet61cc8522020-04-20 14:54:42 +02002104 if (htx->flags & HTX_FL_PARSING_ERROR) {
2105 status = HCHK_STATUS_L7RSP;
2106 goto error;
2107 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002108
Christopher Faulet61cc8522020-04-20 14:54:42 +02002109 if (htx_is_empty(htx)) {
2110 if (last_read) {
2111 status = HCHK_STATUS_L7RSP;
2112 goto error;
2113 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002114 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002115 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002116
Christopher Faulet61cc8522020-04-20 14:54:42 +02002117 sl = http_get_stline(htx);
2118 check->code = sl->info.res.status;
2119
2120 if (check->server &&
2121 (check->server->proxy->options & PR_O_DISABLE404) &&
2122 (check->server->next_state != SRV_ST_STOPPED) &&
2123 (check->code == 404)) {
2124 /* 404 may be accepted as "stopping" only if the server was up */
2125 goto out;
2126 }
2127
2128 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2129 /* Make GCC happy ; initialize match to a failure state. */
2130 match = inverse;
2131
2132 switch (expect->type) {
2133 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002134 match = 0;
2135 for (i = 0; i < expect->codes.num; i++) {
2136 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2137 sl->info.res.status <= expect->codes.codes[i][1]) {
2138 match = 1;
2139 break;
2140 }
2141 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002142
2143 /* Set status and description in case of error */
2144 status = HCHK_STATUS_L7STS;
2145 desc = htx_sl_res_reason(sl);
2146 break;
2147 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2148 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2149
2150 /* Set status and description in case of error */
2151 status = HCHK_STATUS_L7STS;
2152 desc = htx_sl_res_reason(sl);
2153 break;
2154
2155 case TCPCHK_EXPECT_HTTP_BODY:
2156 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2157 chunk_reset(&trash);
2158 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2159 enum htx_blk_type type = htx_get_blk_type(blk);
2160
2161 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2162 break;
2163 if (type == HTX_BLK_DATA) {
2164 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2165 break;
2166 }
2167 }
2168
2169 if (!b_data(&trash)) {
2170 if (!last_read)
2171 goto wait_more_data;
2172 status = HCHK_STATUS_L7RSP;
2173 desc = ist("HTTP content check could not find a response body");
2174 goto error;
2175 }
2176
2177 if (!last_read &&
2178 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2179 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2180 ret = TCPCHK_EVAL_WAIT;
2181 goto out;
2182 }
2183
2184 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002185 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002186 else
2187 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2188
2189 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002190 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002191 desc = (inverse
2192 ? ist("HTTP check matched unwanted content")
2193 : ist("HTTP content check did not match"));
2194 break;
2195
2196 default:
2197 /* should never happen */
2198 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002199 goto error;
2200 }
2201
Christopher Faulet61cc8522020-04-20 14:54:42 +02002202 /* Wait for more data on mismatch only if no minimum is defined (-1),
2203 * otherwise the absence of match is already conclusive.
2204 */
2205 if (!match && !last_read && (expect->min_recv == -1)) {
2206 ret = TCPCHK_EVAL_WAIT;
2207 goto out;
2208 }
2209
2210 if (!(match ^ inverse))
2211 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002212
2213 out:
2214 free_trash_chunk(msg);
2215 return ret;
2216
2217 error:
2218 ret = TCPCHK_EVAL_STOP;
2219 msg = alloc_trash_chunk();
2220 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002221 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002222 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2223 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002224
2225 wait_more_data:
2226 ret = TCPCHK_EVAL_WAIT;
2227 goto out;
2228}
2229
Christopher Faulet61cc8522020-04-20 14:54:42 +02002230/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2231 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2232 * if an error occurred.
2233 */
2234static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2235{
2236 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2237 struct tcpcheck_expect *expect = &rule->expect;
2238 struct buffer *msg = NULL;
2239 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002240
Christopher Faulet61cc8522020-04-20 14:54:42 +02002241 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002242
Christopher Faulet61cc8522020-04-20 14:54:42 +02002243 /* The current expect might need more data than the previous one, check again
2244 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002245 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002246 if (!last_read) {
2247 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2248 (b_data(&check->bi) < istlen(expect->data))) {
2249 ret = TCPCHK_EVAL_WAIT;
2250 goto out;
2251 }
2252 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2253 ret = TCPCHK_EVAL_WAIT;
2254 goto out;
2255 }
2256 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002257
Christopher Faulet61cc8522020-04-20 14:54:42 +02002258 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2259 /* Make GCC happy ; initialize match to a failure state. */
2260 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002261
Christopher Faulet61cc8522020-04-20 14:54:42 +02002262 switch (expect->type) {
2263 case TCPCHK_EXPECT_STRING:
2264 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002265 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 +02002266 break;
2267 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002268 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 +02002269 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002270
Christopher Faulet61cc8522020-04-20 14:54:42 +02002271 case TCPCHK_EXPECT_REGEX_BINARY:
2272 chunk_reset(&trash);
2273 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002274 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002275 break;
2276 case TCPCHK_EXPECT_CUSTOM:
2277 if (expect->custom)
2278 ret = expect->custom(check, rule, last_read);
2279 goto out;
2280 default:
2281 /* Should never happen. */
2282 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002283 goto out;
2284 }
2285
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002286
Christopher Faulet61cc8522020-04-20 14:54:42 +02002287 /* Wait for more data on mismatch only if no minimum is defined (-1),
2288 * otherwise the absence of match is already conclusive.
2289 */
2290 if (!match && !last_read && (expect->min_recv == -1)) {
2291 ret = TCPCHK_EVAL_WAIT;
2292 goto out;
2293 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002294
Christopher Faulet61cc8522020-04-20 14:54:42 +02002295 /* Result as expected, next rule. */
2296 if (match ^ inverse)
2297 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002298
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002299
Christopher Faulet61cc8522020-04-20 14:54:42 +02002300 /* From this point on, we matched something we did not want, this is an error state. */
2301 ret = TCPCHK_EVAL_STOP;
2302 msg = alloc_trash_chunk();
2303 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002304 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002305 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2306 free_trash_chunk(msg);
2307 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002308
Christopher Faulet61cc8522020-04-20 14:54:42 +02002309 out:
2310 return ret;
2311}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002312
Christopher Faulet61cc8522020-04-20 14:54:42 +02002313/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2314 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2315 * waits.
2316 */
2317static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2318{
2319 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2320 struct act_rule *act_rule;
2321 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002322
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323 act_rule =rule->action_kw.rule;
2324 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2325 if (act_ret != ACT_RET_CONT) {
2326 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2327 tcpcheck_get_step_id(check, rule));
2328 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2329 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002330 }
2331
Christopher Faulet61cc8522020-04-20 14:54:42 +02002332 return ret;
2333}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002334
Christopher Faulet61cc8522020-04-20 14:54:42 +02002335/* Executes a tcp-check ruleset. Note that this is called both from the
2336 * connection's wake() callback and from the check scheduling task. It returns
2337 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2338 * presenting the risk of an fd replacement.
2339 *
2340 * Please do NOT place any return statement in this function and only leave
2341 * via the out_end_tcpcheck label after setting retcode.
2342 */
2343static int tcpcheck_main(struct check *check)
2344{
2345 struct tcpcheck_rule *rule;
2346 struct conn_stream *cs = check->cs;
2347 struct connection *conn = cs_conn(cs);
2348 int must_read = 1, last_read = 0;
2349 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002350
Christopher Faulet61cc8522020-04-20 14:54:42 +02002351 /* here, we know that the check is complete or that it failed */
2352 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002353 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002354
Christopher Faulet61cc8522020-04-20 14:54:42 +02002355 /* 1- check for connection error, if any */
2356 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2357 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002358
Christopher Faulet61cc8522020-04-20 14:54:42 +02002359 /* 2- check if we are waiting for the connection establishment. It only
2360 * happens during TCPCHK_ACT_CONNECT. */
2361 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2362 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2363 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2364 if (rule->action == TCPCHK_ACT_SEND)
2365 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2366 else if (rule->action == TCPCHK_ACT_EXPECT)
2367 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2368 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002369 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002370 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002371
2372 /* 3- check for pending outgoing data. It only happens during
2373 * TCPCHK_ACT_SEND. */
2374 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2375 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002376 ret = conn->mux->snd_buf(cs, &check->bo,
2377 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002378 if (ret <= 0) {
2379 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2380 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002381 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002382 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002383 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2384 goto out;
2385 }
2386 }
2387 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002388 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002389
Christopher Faulet61cc8522020-04-20 14:54:42 +02002390 /* 4- check if a rule must be resume. It happens if check->current_step
2391 * is defined. */
2392 else if (check->current_step)
2393 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002394
Christopher Faulet61cc8522020-04-20 14:54:42 +02002395 /* 5- It is the first evaluation. We must create a session and preset
2396 * tcp-check variables */
2397 else {
2398 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002399
Christopher Faulet61cc8522020-04-20 14:54:42 +02002400 /* First evaluation, create a session */
2401 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2402 if (!check->sess) {
2403 chunk_printf(&trash, "TCPCHK error allocating check session");
2404 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2405 goto out_end_tcpcheck;
2406 }
2407 vars_init(&check->vars, SCOPE_CHECK);
2408 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002409
Christopher Faulet61cc8522020-04-20 14:54:42 +02002410 /* Preset tcp-check variables */
2411 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2412 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002413
Christopher Faulet61cc8522020-04-20 14:54:42 +02002414 memset(&smp, 0, sizeof(smp));
2415 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2416 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002417 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002418 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002419 }
2420
Christopher Faulet61cc8522020-04-20 14:54:42 +02002421 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002422
Christopher Faulet61cc8522020-04-20 14:54:42 +02002423 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2424 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002425
Christopher Faulet61cc8522020-04-20 14:54:42 +02002426 check->code = 0;
2427 switch (rule->action) {
2428 case TCPCHK_ACT_CONNECT:
2429 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002430
Christopher Faulet61cc8522020-04-20 14:54:42 +02002431 /* close but not release yet previous connection */
2432 if (check->cs) {
2433 cs_close(check->cs);
2434 retcode = -1; /* do not reuse the fd in the caller! */
2435 }
2436 eval_ret = tcpcheck_eval_connect(check, rule);
2437 must_read = 1; last_read = 0;
2438 break;
2439 case TCPCHK_ACT_SEND:
2440 check->current_step = rule;
2441 eval_ret = tcpcheck_eval_send(check, rule);
2442 must_read = 1;
2443 break;
2444 case TCPCHK_ACT_EXPECT:
2445 check->current_step = rule;
2446 if (must_read) {
2447 if (check->proxy->timeout.check)
2448 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002449
Christopher Faulet61cc8522020-04-20 14:54:42 +02002450 eval_ret = tcpcheck_eval_recv(check, rule);
2451 if (eval_ret == TCPCHK_EVAL_STOP)
2452 goto out_end_tcpcheck;
2453 else if (eval_ret == TCPCHK_EVAL_WAIT)
2454 goto out;
2455 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2456 must_read = 0;
2457 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002458
Christopher Faulet61cc8522020-04-20 14:54:42 +02002459 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2460 ? tcpcheck_eval_expect_http(check, rule, last_read)
2461 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002462
Christopher Faulet61cc8522020-04-20 14:54:42 +02002463 if (eval_ret == TCPCHK_EVAL_WAIT) {
2464 check->current_step = rule->expect.head;
2465 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2466 }
2467 break;
2468 case TCPCHK_ACT_ACTION_KW:
2469 /* Don't update the current step */
2470 eval_ret = tcpcheck_eval_action_kw(check, rule);
2471 break;
2472 default:
2473 /* Otherwise, just go to the next one and don't update
2474 * the current step
2475 */
2476 eval_ret = TCPCHK_EVAL_CONTINUE;
2477 break;
2478 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002479
Christopher Faulet61cc8522020-04-20 14:54:42 +02002480 switch (eval_ret) {
2481 case TCPCHK_EVAL_CONTINUE:
2482 break;
2483 case TCPCHK_EVAL_WAIT:
2484 goto out;
2485 case TCPCHK_EVAL_STOP:
2486 goto out_end_tcpcheck;
2487 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002488 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002489
Christopher Faulet61cc8522020-04-20 14:54:42 +02002490 /* All rules was evaluated */
2491 if (check->current_step) {
2492 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002493
Christopher Faulet61cc8522020-04-20 14:54:42 +02002494 if (rule->action == TCPCHK_ACT_EXPECT) {
2495 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002496
Christopher Faulet61cc8522020-04-20 14:54:42 +02002497 if (check->server &&
2498 (check->server->proxy->options & PR_O_DISABLE404) &&
2499 (check->server->next_state != SRV_ST_STOPPED) &&
2500 (check->code == 404)) {
2501 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2502 goto out_end_tcpcheck;
2503 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002504
Christopher Faulet61cc8522020-04-20 14:54:42 +02002505 msg = alloc_trash_chunk();
2506 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002507 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002508 set_server_check_status(check, rule->expect.ok_status,
2509 (msg ? b_head(msg) : "(tcp-check)"));
2510 free_trash_chunk(msg);
2511 }
2512 else if (rule->action == TCPCHK_ACT_CONNECT) {
2513 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002514 enum healthcheck_status status = HCHK_STATUS_L4OK;
2515#ifdef USE_OPENSSL
2516 if (conn && ssl_sock_is_ssl(conn))
2517 status = HCHK_STATUS_L6OK;
2518#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002519 set_server_check_status(check, status, msg);
2520 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002521 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002522 else
2523 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002524
Christopher Faulet61cc8522020-04-20 14:54:42 +02002525 out_end_tcpcheck:
2526 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2527 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002528
Christopher Faulet61cc8522020-04-20 14:54:42 +02002529 out:
2530 return retcode;
2531}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002532
Christopher Faulet14cd3162020-04-16 14:50:06 +02002533
Christopher Faulet61cc8522020-04-20 14:54:42 +02002534/**************************************************************************/
2535/************** Health-checks based on an external process ****************/
2536/**************************************************************************/
2537static struct list pid_list = LIST_HEAD_INIT(pid_list);
2538static struct pool_head *pool_head_pid_list;
2539__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002540
Christopher Faulet61cc8522020-04-20 14:54:42 +02002541struct extcheck_env {
2542 char *name; /* environment variable name */
2543 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2544};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002545
Christopher Faulet61cc8522020-04-20 14:54:42 +02002546/* environment variables memory requirement for different types of data */
2547#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2548 * such environment variables are not updatable. */
2549#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2550#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2551#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002552
Christopher Faulet61cc8522020-04-20 14:54:42 +02002553/* external checks environment variables */
2554enum {
2555 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002556
Christopher Faulet61cc8522020-04-20 14:54:42 +02002557 /* Proxy specific environment variables */
2558 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2559 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2560 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2561 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002562
Christopher Faulet61cc8522020-04-20 14:54:42 +02002563 /* Server specific environment variables */
2564 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2565 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2566 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2567 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2568 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2569 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002570
Christopher Faulet61cc8522020-04-20 14:54:42 +02002571 EXTCHK_SIZE
2572};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002573
Christopher Faulet61cc8522020-04-20 14:54:42 +02002574const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2575 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2576 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2577 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2578 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2579 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2580 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2581 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2582 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2583 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2584 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2585 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2586};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002587
Christopher Faulet61cc8522020-04-20 14:54:42 +02002588void block_sigchld(void)
2589{
2590 sigset_t set;
2591 sigemptyset(&set);
2592 sigaddset(&set, SIGCHLD);
2593 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2594}
Willy Tarreaube373152018-09-06 11:45:30 +02002595
Christopher Faulet61cc8522020-04-20 14:54:42 +02002596void unblock_sigchld(void)
2597{
2598 sigset_t set;
2599 sigemptyset(&set);
2600 sigaddset(&set, SIGCHLD);
2601 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002602}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002603
Christopher Faulet61cc8522020-04-20 14:54:42 +02002604static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002605{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002606 struct pid_list *elem;
2607 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002608
Christopher Faulet61cc8522020-04-20 14:54:42 +02002609 elem = pool_alloc(pool_head_pid_list);
2610 if (!elem)
2611 return NULL;
2612 elem->pid = pid;
2613 elem->t = t;
2614 elem->exited = 0;
2615 check->curpid = elem;
2616 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002617
Christopher Faulet61cc8522020-04-20 14:54:42 +02002618 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2619 LIST_ADD(&pid_list, &elem->list);
2620 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002621
Christopher Faulet61cc8522020-04-20 14:54:42 +02002622 return elem;
2623}
Christopher Faulete5870d82020-04-15 11:32:03 +02002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625static void pid_list_del(struct pid_list *elem)
2626{
2627 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002628
Christopher Faulet61cc8522020-04-20 14:54:42 +02002629 if (!elem)
2630 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2633 LIST_DEL(&elem->list);
2634 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002635
Christopher Faulet61cc8522020-04-20 14:54:42 +02002636 if (!elem->exited)
2637 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002638
Christopher Faulet61cc8522020-04-20 14:54:42 +02002639 check = elem->t->context;
2640 check->curpid = NULL;
2641 pool_free(pool_head_pid_list, elem);
2642}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002643
Christopher Faulet61cc8522020-04-20 14:54:42 +02002644/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2645static void pid_list_expire(pid_t pid, int status)
2646{
2647 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002648
Christopher Faulet61cc8522020-04-20 14:54:42 +02002649 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2650 list_for_each_entry(elem, &pid_list, list) {
2651 if (elem->pid == pid) {
2652 elem->t->expire = now_ms;
2653 elem->status = status;
2654 elem->exited = 1;
2655 task_wakeup(elem->t, TASK_WOKEN_IO);
2656 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002657 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002658 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002659 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2660}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002661
Christopher Faulet61cc8522020-04-20 14:54:42 +02002662static void sigchld_handler(struct sig_handler *sh)
2663{
2664 pid_t pid;
2665 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002666
Christopher Faulet61cc8522020-04-20 14:54:42 +02002667 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2668 pid_list_expire(pid, status);
2669}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002670
Christopher Faulet61cc8522020-04-20 14:54:42 +02002671static int init_pid_list(void)
2672{
2673 if (pool_head_pid_list != NULL)
2674 /* Nothing to do */
2675 return 0;
2676
2677 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2678 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2679 strerror(errno));
2680 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002681 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002682
Christopher Faulet61cc8522020-04-20 14:54:42 +02002683 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2684 if (pool_head_pid_list == NULL) {
2685 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2686 strerror(errno));
2687 return 1;
2688 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002689
Christopher Faulet61cc8522020-04-20 14:54:42 +02002690 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002691}
2692
Christopher Faulet61cc8522020-04-20 14:54:42 +02002693/* helper macro to set an environment variable and jump to a specific label on failure. */
2694#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002695
Christopher Faulet61cc8522020-04-20 14:54:42 +02002696/*
2697 * helper function to allocate enough memory to store an environment variable.
2698 * It will also check that the environment variable is updatable, and silently
2699 * fail if not.
2700 */
2701static int extchk_setenv(struct check *check, int idx, const char *value)
2702{
2703 int len, ret;
2704 char *envname;
2705 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002706
Christopher Faulet61cc8522020-04-20 14:54:42 +02002707 if (idx < 0 || idx >= EXTCHK_SIZE) {
2708 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2709 return 1;
2710 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002711
Christopher Faulet61cc8522020-04-20 14:54:42 +02002712 envname = extcheck_envs[idx].name;
2713 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002714
Christopher Faulet61cc8522020-04-20 14:54:42 +02002715 /* Check if the environment variable is already set, and silently reject
2716 * the update if this one is not updatable. */
2717 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2718 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002719
Christopher Faulet61cc8522020-04-20 14:54:42 +02002720 /* Instead of sending NOT_USED, sending an empty value is preferable */
2721 if (strcmp(value, "NOT_USED") == 0) {
2722 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002723 }
2724
Christopher Faulet61cc8522020-04-20 14:54:42 +02002725 len = strlen(envname) + 1;
2726 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2727 len += strlen(value);
2728 else
2729 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002730
Christopher Faulet61cc8522020-04-20 14:54:42 +02002731 if (!check->envp[idx])
2732 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002733
Christopher Faulet61cc8522020-04-20 14:54:42 +02002734 if (!check->envp[idx]) {
2735 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2736 return 1;
2737 }
2738 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2739 if (ret < 0) {
2740 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2741 return 1;
2742 }
2743 else if (ret > len) {
2744 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2745 return 1;
2746 }
2747 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002748}
2749
Christopher Faulet61cc8522020-04-20 14:54:42 +02002750static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002751{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752 struct server *s = check->server;
2753 struct proxy *px = s->proxy;
2754 struct listener *listener = NULL, *l;
2755 int i;
2756 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2757 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002758
Christopher Faulet61cc8522020-04-20 14:54:42 +02002759 list_for_each_entry(l, &px->conf.listeners, by_fe)
2760 /* Use the first INET, INET6 or UNIX listener */
2761 if (l->addr.ss_family == AF_INET ||
2762 l->addr.ss_family == AF_INET6 ||
2763 l->addr.ss_family == AF_UNIX) {
2764 listener = l;
2765 break;
2766 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002767
Christopher Faulet61cc8522020-04-20 14:54:42 +02002768 check->curpid = NULL;
2769 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2770 if (!check->envp) {
2771 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2772 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002773 }
2774
Christopher Faulet61cc8522020-04-20 14:54:42 +02002775 check->argv = calloc(6, sizeof(char *));
2776 if (!check->argv) {
2777 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2778 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002779 }
2780
Christopher Faulet61cc8522020-04-20 14:54:42 +02002781 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002782
Christopher Faulet61cc8522020-04-20 14:54:42 +02002783 if (!listener) {
2784 check->argv[1] = strdup("NOT_USED");
2785 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002786 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002787 else if (listener->addr.ss_family == AF_INET ||
2788 listener->addr.ss_family == AF_INET6) {
2789 addr_to_str(&listener->addr, buf, sizeof(buf));
2790 check->argv[1] = strdup(buf);
2791 port_to_str(&listener->addr, buf, sizeof(buf));
2792 check->argv[2] = strdup(buf);
2793 }
2794 else if (listener->addr.ss_family == AF_UNIX) {
2795 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002796
Christopher Faulet61cc8522020-04-20 14:54:42 +02002797 un = (struct sockaddr_un *)&listener->addr;
2798 check->argv[1] = strdup(un->sun_path);
2799 check->argv[2] = strdup("NOT_USED");
2800 }
2801 else {
2802 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2803 goto err;
2804 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002805
Christopher Faulet61cc8522020-04-20 14:54:42 +02002806 if (!check->argv[1] || !check->argv[2]) {
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 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2812 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2813 if (!check->argv[3] || !check->argv[4]) {
2814 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2815 goto err;
2816 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002817
Christopher Faulet61cc8522020-04-20 14:54:42 +02002818 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2819 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2820 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002821
Christopher Faulet61cc8522020-04-20 14:54:42 +02002822 for (i = 0; i < 5; i++) {
2823 if (!check->argv[i]) {
2824 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2825 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002826 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002827 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002828
Christopher Faulet61cc8522020-04-20 14:54:42 +02002829 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2830 /* Add proxy environment variables */
2831 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2832 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2833 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2834 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2835 /* Add server environment variables */
2836 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2837 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2838 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2839 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2840 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2841 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002842
Christopher Faulet61cc8522020-04-20 14:54:42 +02002843 /* Ensure that we don't leave any hole in check->envp */
2844 for (i = 0; i < EXTCHK_SIZE; i++)
2845 if (!check->envp[i])
2846 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002847
Christopher Faulet61cc8522020-04-20 14:54:42 +02002848 return 1;
2849err:
2850 if (check->envp) {
2851 for (i = 0; i < EXTCHK_SIZE; i++)
2852 free(check->envp[i]);
2853 free(check->envp);
2854 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002855 }
2856
Christopher Faulet61cc8522020-04-20 14:54:42 +02002857 if (check->argv) {
2858 for (i = 1; i < 5; i++)
2859 free(check->argv[i]);
2860 free(check->argv);
2861 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002862 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002863 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002864}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002865
Christopher Faulet61cc8522020-04-20 14:54:42 +02002866/*
2867 * establish a server health-check that makes use of a process.
2868 *
2869 * It can return one of :
2870 * - SF_ERR_NONE if everything's OK
2871 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2872 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2873 *
2874 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002875 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002876static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002877{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002878 char buf[256];
2879 struct check *check = t->context;
2880 struct server *s = check->server;
2881 struct proxy *px = s->proxy;
2882 int status;
2883 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002884
Christopher Faulet61cc8522020-04-20 14:54:42 +02002885 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002886
Christopher Faulet61cc8522020-04-20 14:54:42 +02002887 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002888
Christopher Faulet61cc8522020-04-20 14:54:42 +02002889 pid = fork();
2890 if (pid < 0) {
2891 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2892 (global.tune.options & GTUNE_INSECURE_FORK) ?
2893 "" : " (likely caused by missing 'insecure-fork-wanted')",
2894 strerror(errno));
2895 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002896 goto out;
2897 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002898 if (pid == 0) {
2899 /* Child */
2900 extern char **environ;
2901 struct rlimit limit;
2902 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002903
Christopher Faulet61cc8522020-04-20 14:54:42 +02002904 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2905 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002906
Christopher Faulet61cc8522020-04-20 14:54:42 +02002907 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002908
Christopher Faulet61cc8522020-04-20 14:54:42 +02002909 /* restore the initial FD limits */
2910 limit.rlim_cur = rlim_fd_cur_at_boot;
2911 limit.rlim_max = rlim_fd_max_at_boot;
2912 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2913 getrlimit(RLIMIT_NOFILE, &limit);
2914 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2915 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2916 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2917 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002918
Christopher Faulet61cc8522020-04-20 14:54:42 +02002919 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002920
Christopher Faulet61cc8522020-04-20 14:54:42 +02002921 /* Update some environment variables and command args: curconn, server addr and server port */
2922 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002923
Christopher Faulet61cc8522020-04-20 14:54:42 +02002924 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2925 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002926
Christopher Faulet61cc8522020-04-20 14:54:42 +02002927 *check->argv[4] = 0;
2928 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2929 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2930 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002931
Christopher Faulet61cc8522020-04-20 14:54:42 +02002932 haproxy_unblock_signals();
2933 execvp(px->check_command, check->argv);
2934 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2935 strerror(errno));
2936 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002937 }
2938
Christopher Faulet61cc8522020-04-20 14:54:42 +02002939 /* Parent */
2940 if (check->result == CHK_RES_UNKNOWN) {
2941 if (pid_list_add(pid, t) != NULL) {
2942 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2943
2944 if (px->timeout.check && px->timeout.connect) {
2945 int t_con = tick_add(now_ms, px->timeout.connect);
2946 t->expire = tick_first(t->expire, t_con);
2947 }
2948 status = SF_ERR_NONE;
2949 goto out;
2950 }
2951 else {
2952 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2953 }
2954 kill(pid, SIGTERM); /* process creation error */
2955 }
2956 else
2957 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2958
2959out:
2960 unblock_sigchld();
2961 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002962}
2963
Christopher Faulet61cc8522020-04-20 14:54:42 +02002964/*
2965 * manages a server health-check that uses an external process. Returns
2966 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002967 *
2968 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002969 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002970 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002971static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002972{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002973 struct check *check = context;
2974 struct server *s = check->server;
2975 int rv;
2976 int ret;
2977 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002978
Christopher Faulet61cc8522020-04-20 14:54:42 +02002979 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2980 if (!(check->state & CHK_ST_INPROGRESS)) {
2981 /* no check currently running */
2982 if (!expired) /* woke up too early */
2983 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002984
Christopher Faulet61cc8522020-04-20 14:54:42 +02002985 /* we don't send any health-checks when the proxy is
2986 * stopped, the server should not be checked or the check
2987 * is disabled.
2988 */
2989 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2990 s->proxy->state == PR_STSTOPPED)
2991 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002992
Christopher Faulet61cc8522020-04-20 14:54:42 +02002993 /* we'll initiate a new check */
2994 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002995
Christopher Faulet61cc8522020-04-20 14:54:42 +02002996 check->state |= CHK_ST_INPROGRESS;
2997
2998 ret = connect_proc_chk(t);
2999 if (ret == SF_ERR_NONE) {
3000 /* the process was forked, we allow up to min(inter,
3001 * timeout.connect) for it to report its status, but
3002 * only when timeout.check is set as it may be to short
3003 * for a full check otherwise.
3004 */
3005 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3006
3007 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3008 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3009 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003010 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003011 task_set_affinity(t, tid_bit);
3012 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003013 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003014
Christopher Faulet61cc8522020-04-20 14:54:42 +02003015 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003016
Christopher Faulet61cc8522020-04-20 14:54:42 +02003017 check->state &= ~CHK_ST_INPROGRESS;
3018 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003019
Christopher Faulet61cc8522020-04-20 14:54:42 +02003020 /* we allow up to min(inter, timeout.connect) for a connection
3021 * to establish but only when timeout.check is set
3022 * as it may be to short for a full check otherwise
3023 */
3024 while (tick_is_expired(t->expire, now_ms)) {
3025 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003026
Christopher Faulet61cc8522020-04-20 14:54:42 +02003027 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3028 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003029
Christopher Faulet61cc8522020-04-20 14:54:42 +02003030 if (s->proxy->timeout.check)
3031 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003032 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003033 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003034 else {
3035 /* there was a test running.
3036 * First, let's check whether there was an uncaught error,
3037 * which can happen on connect timeout or error.
3038 */
3039 if (check->result == CHK_RES_UNKNOWN) {
3040 /* good connection is enough for pure TCP check */
3041 struct pid_list *elem = check->curpid;
3042 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003043
Christopher Faulet61cc8522020-04-20 14:54:42 +02003044 if (elem->exited) {
3045 status = elem->status; /* Save in case the process exits between use below */
3046 if (!WIFEXITED(status))
3047 check->code = -1;
3048 else
3049 check->code = WEXITSTATUS(status);
3050 if (!WIFEXITED(status) || WEXITSTATUS(status))
3051 status = HCHK_STATUS_PROCERR;
3052 else
3053 status = HCHK_STATUS_PROCOK;
3054 } else if (expired) {
3055 status = HCHK_STATUS_PROCTOUT;
3056 ha_warning("kill %d\n", (int)elem->pid);
3057 kill(elem->pid, SIGTERM);
3058 }
3059 set_server_check_status(check, status, NULL);
3060 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003061
Christopher Faulet61cc8522020-04-20 14:54:42 +02003062 if (check->result == CHK_RES_FAILED) {
3063 /* a failure or timeout detected */
3064 check_notify_failure(check);
3065 }
3066 else if (check->result == CHK_RES_CONDPASS) {
3067 /* check is OK but asks for stopping mode */
3068 check_notify_stopping(check);
3069 }
3070 else if (check->result == CHK_RES_PASSED) {
3071 /* a success was detected */
3072 check_notify_success(check);
3073 }
3074 task_set_affinity(t, 1);
3075 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003076
Christopher Faulet61cc8522020-04-20 14:54:42 +02003077 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003078
Christopher Faulet61cc8522020-04-20 14:54:42 +02003079 rv = 0;
3080 if (global.spread_checks > 0) {
3081 rv = srv_getinter(check) * global.spread_checks / 100;
3082 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3083 }
3084 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3085 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003086
Christopher Faulet61cc8522020-04-20 14:54:42 +02003087 reschedule:
3088 while (tick_is_expired(t->expire, now_ms))
3089 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003090
Christopher Faulet61cc8522020-04-20 14:54:42 +02003091 out_unlock:
3092 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3093 return t;
3094}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003095
Baptiste Assmann248f1172018-03-01 21:49:01 +01003096
Christopher Faulet61cc8522020-04-20 14:54:42 +02003097/**************************************************************************/
3098/***************** Health-checks based on connections *********************/
3099/**************************************************************************/
3100/* This function is used only for server health-checks. It handles connection
3101 * status updates including errors. If necessary, it wakes the check task up.
3102 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3103 * connection (eg: reconnect). It relies on tcpcheck_main().
3104 */
3105static int wake_srv_chk(struct conn_stream *cs)
3106{
3107 struct connection *conn = cs->conn;
3108 struct check *check = cs->data;
3109 struct email_alertq *q = container_of(check, typeof(*q), check);
3110 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003111
Christopher Faulet61cc8522020-04-20 14:54:42 +02003112 if (check->server)
3113 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3114 else
3115 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003116
Christopher Faulet61cc8522020-04-20 14:54:42 +02003117 /* we may have to make progress on the TCP checks */
3118 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003119
Christopher Faulet61cc8522020-04-20 14:54:42 +02003120 cs = check->cs;
3121 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003122
Christopher Faulet61cc8522020-04-20 14:54:42 +02003123 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3124 /* We may get error reports bypassing the I/O handlers, typically
3125 * the case when sending a pure TCP check which fails, then the I/O
3126 * handlers above are not called. This is completely handled by the
3127 * main processing task so let's simply wake it up. If we get here,
3128 * we expect errno to still be valid.
3129 */
3130 chk_report_conn_err(check, errno, 0);
3131 task_wakeup(check->task, TASK_WOKEN_IO);
3132 }
3133
3134 if (check->result != CHK_RES_UNKNOWN) {
3135 /* Check complete or aborted. If connection not yet closed do it
3136 * now and wake the check task up to be sure the result is
3137 * handled ASAP. */
3138 conn_sock_drain(conn);
3139 cs_close(cs);
3140 ret = -1;
3141 /* We may have been scheduled to run, and the
3142 * I/O handler expects to have a cs, so remove
3143 * the tasklet
3144 */
3145 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3146 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003147 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003148
3149 if (check->server)
3150 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003151 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003152 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003153
Christopher Faulet61cc8522020-04-20 14:54:42 +02003154 /* if a connection got replaced, we must absolutely prevent the connection
3155 * handler from touching its fd, and perform the FD polling updates ourselves
3156 */
3157 if (ret < 0)
3158 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003159
Christopher Faulet61cc8522020-04-20 14:54:42 +02003160 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003161}
3162
Christopher Faulet61cc8522020-04-20 14:54:42 +02003163/* This function checks if any I/O is wanted, and if so, attempts to do so */
3164static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003165{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003166 struct check *check = ctx;
3167 struct conn_stream *cs = check->cs;
3168 struct email_alertq *q = container_of(check, typeof(*q), check);
3169 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003170
Christopher Faulet61cc8522020-04-20 14:54:42 +02003171 if (!(check->wait_list.events & SUB_RETRY_SEND))
3172 ret = wake_srv_chk(cs);
3173 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3174 if (check->server)
3175 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3176 else
3177 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003178
Christopher Faulet61cc8522020-04-20 14:54:42 +02003179 if (unlikely(check->result == CHK_RES_FAILED)) {
3180 /* collect possible new errors */
3181 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3182 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003183
Christopher Faulet61cc8522020-04-20 14:54:42 +02003184 /* Reset the check buffer... */
3185 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003186
Christopher Faulet61cc8522020-04-20 14:54:42 +02003187 /* Close the connection... We still attempt to nicely close if,
3188 * for instance, SSL needs to send a "close notify." Later, we perform
3189 * a hard close and reset the connection if some data are pending,
3190 * otherwise we end up with many TIME_WAITs and eat all the source port
3191 * range quickly. To avoid sending RSTs all the time, we first try to
3192 * drain pending data.
3193 */
3194 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3195 * connection, to make sure cs_shutw() will not lead to a shutdown()
3196 * that would provoke TIME_WAITs.
3197 */
3198 cs_shutr(cs, CS_SHR_DRAIN);
3199 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003200
Christopher Faulet61cc8522020-04-20 14:54:42 +02003201 /* OK, let's not stay here forever */
3202 if (check->result == CHK_RES_FAILED)
3203 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003204
Christopher Faulet61cc8522020-04-20 14:54:42 +02003205 task_wakeup(t, TASK_WOKEN_IO);
3206 }
3207
3208 if (check->server)
3209 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3210 else
3211 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003212 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003213 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003214}
3215
Christopher Faulet61cc8522020-04-20 14:54:42 +02003216/* manages a server health-check that uses a connection. Returns
3217 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3218 *
3219 * Please do NOT place any return statement in this function and only leave
3220 * via the out_unlock label.
3221 */
3222static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003223{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003224 struct check *check = context;
3225 struct proxy *proxy = check->proxy;
3226 struct conn_stream *cs = check->cs;
3227 struct connection *conn = cs_conn(cs);
3228 int rv;
3229 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003230
Christopher Faulet61cc8522020-04-20 14:54:42 +02003231 if (check->server)
3232 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3233 if (!(check->state & CHK_ST_INPROGRESS)) {
3234 /* no check currently running */
3235 if (!expired) /* woke up too early */
3236 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003237
Christopher Faulet61cc8522020-04-20 14:54:42 +02003238 /* we don't send any health-checks when the proxy is
3239 * stopped, the server should not be checked or the check
3240 * is disabled.
3241 */
3242 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3243 proxy->state == PR_STSTOPPED)
3244 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003245
Christopher Faulet61cc8522020-04-20 14:54:42 +02003246 /* we'll initiate a new check */
3247 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003248
Christopher Faulet61cc8522020-04-20 14:54:42 +02003249 check->state |= CHK_ST_INPROGRESS;
3250 b_reset(&check->bi);
3251 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003252
Christopher Faulet61cc8522020-04-20 14:54:42 +02003253 task_set_affinity(t, tid_bit);
3254 cs = check->cs;
3255 conn = cs_conn(cs);
3256 if (!conn) {
3257 check->current_step = NULL;
3258 tcpcheck_main(check);
3259 goto out_unlock;
3260 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003261
Christopher Faulet61cc8522020-04-20 14:54:42 +02003262 conn->flags |= CO_FL_ERROR;
3263 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003264
Christopher Faulet61cc8522020-04-20 14:54:42 +02003265 /* here, we have seen a synchronous error, no fd was allocated */
3266 task_set_affinity(t, MAX_THREADS_MASK);
3267 if (cs) {
3268 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003269 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003270 /* We may have been scheduled to run, and the
3271 * I/O handler expects to have a cs, so remove
3272 * the tasklet
3273 */
3274 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3275 cs_destroy(cs);
3276 cs = check->cs = NULL;
3277 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003278 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003279
Christopher Faulet61cc8522020-04-20 14:54:42 +02003280 check->state &= ~CHK_ST_INPROGRESS;
3281 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003282
Christopher Faulet61cc8522020-04-20 14:54:42 +02003283 /* we allow up to min(inter, timeout.connect) for a connection
3284 * to establish but only when timeout.check is set
3285 * as it may be to short for a full check otherwise
3286 */
3287 while (tick_is_expired(t->expire, now_ms)) {
3288 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003289
Christopher Faulet61cc8522020-04-20 14:54:42 +02003290 t_con = tick_add(t->expire, proxy->timeout.connect);
3291 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3292 if (proxy->timeout.check)
3293 t->expire = tick_first(t->expire, t_con);
3294 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003295 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003296 else {
3297 /* there was a test running.
3298 * First, let's check whether there was an uncaught error,
3299 * which can happen on connect timeout or error.
3300 */
3301 if (check->result == CHK_RES_UNKNOWN) {
3302 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3303 chk_report_conn_err(check, 0, expired);
3304 }
3305 else
3306 goto out_unlock; /* timeout not reached, wait again */
3307 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003308
Christopher Faulet61cc8522020-04-20 14:54:42 +02003309 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003310
Christopher Faulet61cc8522020-04-20 14:54:42 +02003311 check->current_step = NULL;
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)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003325 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003326 /* We may have been scheduled to run, and the
3327 * I/O handler expects to have a cs, so remove
3328 * the tasklet
3329 */
3330 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3331 cs_destroy(cs);
3332 cs = check->cs = NULL;
3333 conn = NULL;
3334 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003335
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003336 if (check->sess != NULL) {
3337 vars_prune(&check->vars, check->sess, NULL);
3338 session_free(check->sess);
3339 check->sess = NULL;
3340 }
3341
Christopher Faulet61cc8522020-04-20 14:54:42 +02003342 if (check->server) {
3343 if (check->result == CHK_RES_FAILED) {
3344 /* a failure or timeout detected */
3345 check_notify_failure(check);
3346 }
3347 else if (check->result == CHK_RES_CONDPASS) {
3348 /* check is OK but asks for stopping mode */
3349 check_notify_stopping(check);
3350 }
3351 else if (check->result == CHK_RES_PASSED) {
3352 /* a success was detected */
3353 check_notify_success(check);
3354 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003355 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003356 task_set_affinity(t, MAX_THREADS_MASK);
3357 check->state &= ~CHK_ST_INPROGRESS;
3358
3359 if (check->server) {
3360 rv = 0;
3361 if (global.spread_checks > 0) {
3362 rv = srv_getinter(check) * global.spread_checks / 100;
3363 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3364 }
3365 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003366 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003367 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003368
Christopher Faulet61cc8522020-04-20 14:54:42 +02003369 reschedule:
3370 while (tick_is_expired(t->expire, now_ms))
3371 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3372 out_unlock:
3373 if (check->server)
3374 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3375 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003376}
3377
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003378
Christopher Faulet61cc8522020-04-20 14:54:42 +02003379/**************************************************************************/
3380/******************* Internals to parse tcp-check rules *******************/
3381/**************************************************************************/
3382struct action_kw_list tcp_check_keywords = {
3383 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3384};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003385
Christopher Faulet61cc8522020-04-20 14:54:42 +02003386/* Return the struct action_kw associated to a keyword */
3387static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003388{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003389 return action_lookup(&tcp_check_keywords.list, kw);
3390}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003391
Christopher Faulet61cc8522020-04-20 14:54:42 +02003392static void action_kw_tcp_check_build_list(struct buffer *chk)
3393{
3394 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003395}
3396
Christopher Faulet61cc8522020-04-20 14:54:42 +02003397/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3398 * returned on error.
3399 */
3400static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3401 struct list *rules, struct action_kw *kw,
3402 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003403{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003404 struct tcpcheck_rule *chk = NULL;
3405 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003406
Christopher Faulet61cc8522020-04-20 14:54:42 +02003407 actrule = calloc(1, sizeof(*actrule));
3408 if (!actrule) {
3409 memprintf(errmsg, "out of memory");
3410 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003411 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003412 actrule->kw = kw;
3413 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003414
Christopher Faulet61cc8522020-04-20 14:54:42 +02003415 cur_arg++;
3416 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3417 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3418 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003419 }
3420
Christopher Faulet61cc8522020-04-20 14:54:42 +02003421 chk = calloc(1, sizeof(*chk));
3422 if (!chk) {
3423 memprintf(errmsg, "out of memory");
3424 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003425 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003426 chk->action = TCPCHK_ACT_ACTION_KW;
3427 chk->action_kw.rule = actrule;
3428 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003429
3430 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003431 free(actrule);
3432 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003433}
3434
Christopher Faulet61cc8522020-04-20 14:54:42 +02003435/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3436 * returned on error.
3437 */
3438static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3439 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003440{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003441 struct tcpcheck_rule *chk = NULL;
3442 struct sockaddr_storage *sk = NULL;
3443 char *comment = NULL, *sni = NULL, *alpn = NULL;
3444 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003445 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003446 unsigned short conn_opts = 0;
3447 long port = 0;
3448 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003449
Christopher Faulet61cc8522020-04-20 14:54:42 +02003450 list_for_each_entry(chk, rules, list) {
3451 if (chk->action == TCPCHK_ACT_CONNECT)
3452 break;
3453 if (chk->action == TCPCHK_ACT_COMMENT ||
3454 chk->action == TCPCHK_ACT_ACTION_KW ||
3455 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3456 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003457
Christopher Faulet61cc8522020-04-20 14:54:42 +02003458 memprintf(errmsg, "first step MUST also be a 'connect', "
3459 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3460 "when there is a 'connect' step in the tcp-check ruleset");
3461 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003462 }
3463
Christopher Faulet61cc8522020-04-20 14:54:42 +02003464 cur_arg++;
3465 while (*(args[cur_arg])) {
3466 if (strcmp(args[cur_arg], "default") == 0)
3467 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3468 else if (strcmp(args[cur_arg], "addr") == 0) {
3469 int port1, port2;
3470 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003471
Christopher Faulet61cc8522020-04-20 14:54:42 +02003472 if (!*(args[cur_arg+1])) {
3473 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3474 goto error;
3475 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003476
Christopher Faulet61cc8522020-04-20 14:54:42 +02003477 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3478 if (!sk) {
3479 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3480 goto error;
3481 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003482
Christopher Faulet61cc8522020-04-20 14:54:42 +02003483 proto = protocol_by_family(sk->ss_family);
3484 if (!proto || !proto->connect) {
3485 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3486 args[cur_arg]);
3487 goto error;
3488 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003489
Christopher Faulet61cc8522020-04-20 14:54:42 +02003490 if (port1 != port2) {
3491 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3492 args[cur_arg], args[cur_arg+1]);
3493 goto error;
3494 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003495
Christopher Faulet61cc8522020-04-20 14:54:42 +02003496 cur_arg++;
3497 }
3498 else if (strcmp(args[cur_arg], "port") == 0) {
3499 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003500
Christopher Faulet61cc8522020-04-20 14:54:42 +02003501 if (!*(args[cur_arg+1])) {
3502 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3503 goto error;
3504 }
3505 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003506
Christopher Faulet61cc8522020-04-20 14:54:42 +02003507 port = 0;
3508 release_sample_expr(port_expr);
3509 p = args[cur_arg]; end = p + strlen(p);
3510 port = read_uint(&p, end);
3511 if (p != end) {
3512 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003513
Christopher Faulet61cc8522020-04-20 14:54:42 +02003514 px->conf.args.ctx = ARGC_SRV;
3515 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3516 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003517
Christopher Faulet61cc8522020-04-20 14:54:42 +02003518 if (!port_expr) {
3519 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3520 goto error;
3521 }
3522 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3523 memprintf(errmsg, "error detected while parsing port expression : "
3524 " fetch method '%s' extracts information from '%s', "
3525 "none of which is available here.\n",
3526 args[cur_arg], sample_src_names(port_expr->fetch->use));
3527 goto error;
3528 }
3529 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3530 }
3531 else if (port > 65535 || port < 1) {
3532 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3533 args[cur_arg]);
3534 goto error;
3535 }
3536 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003537 else if (strcmp(args[cur_arg], "proto") == 0) {
3538 if (!*(args[cur_arg+1])) {
3539 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3540 goto error;
3541 }
3542 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3543 if (!mux_proto) {
3544 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3545 goto error;
3546 }
3547 cur_arg++;
3548 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003549 else if (strcmp(args[cur_arg], "comment") == 0) {
3550 if (!*(args[cur_arg+1])) {
3551 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3552 goto error;
3553 }
3554 cur_arg++;
3555 free(comment);
3556 comment = strdup(args[cur_arg]);
3557 if (!comment) {
3558 memprintf(errmsg, "out of memory");
3559 goto error;
3560 }
3561 }
3562 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3563 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3564 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3565 conn_opts |= TCPCHK_OPT_SOCKS4;
3566 else if (strcmp(args[cur_arg], "linger") == 0)
3567 conn_opts |= TCPCHK_OPT_LINGER;
3568#ifdef USE_OPENSSL
3569 else if (strcmp(args[cur_arg], "ssl") == 0) {
3570 px->options |= PR_O_TCPCHK_SSL;
3571 conn_opts |= TCPCHK_OPT_SSL;
3572 }
3573 else if (strcmp(args[cur_arg], "sni") == 0) {
3574 if (!*(args[cur_arg+1])) {
3575 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3576 goto error;
3577 }
3578 cur_arg++;
3579 free(sni);
3580 sni = strdup(args[cur_arg]);
3581 if (!sni) {
3582 memprintf(errmsg, "out of memory");
3583 goto error;
3584 }
3585 }
3586 else if (strcmp(args[cur_arg], "alpn") == 0) {
3587#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3588 free(alpn);
3589 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3590 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3591 goto error;
3592 }
3593 cur_arg++;
3594#else
3595 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003596 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003597#endif
3598 }
3599#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003600
Christopher Faulet61cc8522020-04-20 14:54:42 +02003601 else {
3602 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3603#ifdef USE_OPENSSL
3604 ", 'ssl', 'sni', 'alpn'"
3605#endif /* USE_OPENSSL */
3606 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3607 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003608 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003609 }
3610 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003611 }
3612
Christopher Faulet61cc8522020-04-20 14:54:42 +02003613 chk = calloc(1, sizeof(*chk));
3614 if (!chk) {
3615 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003616 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003617 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003618 chk->action = TCPCHK_ACT_CONNECT;
3619 chk->comment = comment;
3620 chk->connect.port = port;
3621 chk->connect.options = conn_opts;
3622 chk->connect.sni = sni;
3623 chk->connect.alpn = alpn;
3624 chk->connect.alpn_len= alpn_len;
3625 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003626 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003627 if (sk)
3628 chk->connect.addr = *sk;
3629 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003630
Christopher Faulet61cc8522020-04-20 14:54:42 +02003631 error:
3632 free(alpn);
3633 free(sni);
3634 free(comment);
3635 release_sample_expr(port_expr);
3636 return NULL;
3637}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003638
Christopher Faulet61cc8522020-04-20 14:54:42 +02003639/* Parses and creates a tcp-check send rule. NULL is returned on error */
3640static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3641 const char *file, int line, char **errmsg)
3642{
3643 struct tcpcheck_rule *chk = NULL;
3644 char *comment = NULL, *data = NULL;
3645 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003646
Christopher Faulet61cc8522020-04-20 14:54:42 +02003647 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3648 if (!*(args[cur_arg+1])) {
3649 memprintf(errmsg, "'%s' expects a %s as argument",
3650 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003652 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003653
Christopher Faulet61cc8522020-04-20 14:54:42 +02003654 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003655
Christopher Faulet61cc8522020-04-20 14:54:42 +02003656 cur_arg += 2;
3657 while (*(args[cur_arg])) {
3658 if (strcmp(args[cur_arg], "comment") == 0) {
3659 if (!*(args[cur_arg+1])) {
3660 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3661 goto error;
3662 }
3663 cur_arg++;
3664 free(comment);
3665 comment = strdup(args[cur_arg]);
3666 if (!comment) {
3667 memprintf(errmsg, "out of memory");
3668 goto error;
3669 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003670 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003671 else if (strcmp(args[cur_arg], "log-format") == 0) {
3672 if (type == TCPCHK_SEND_BINARY)
3673 type = TCPCHK_SEND_BINARY_LF;
3674 else if (type == TCPCHK_SEND_STRING)
3675 type = TCPCHK_SEND_STRING_LF;
3676 }
3677 else {
3678 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3679 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003680 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003681 }
3682 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003683 }
3684
Christopher Faulet61cc8522020-04-20 14:54:42 +02003685 chk = calloc(1, sizeof(*chk));
3686 if (!chk) {
3687 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003688 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003689 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003690 chk->action = TCPCHK_ACT_SEND;
3691 chk->comment = comment;
3692 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003693
Christopher Faulet61cc8522020-04-20 14:54:42 +02003694 switch (chk->send.type) {
3695 case TCPCHK_SEND_STRING:
3696 chk->send.data = ist2(strdup(data), strlen(data));
3697 if (!isttest(chk->send.data)) {
3698 memprintf(errmsg, "out of memory");
3699 goto error;
3700 }
3701 break;
3702 case TCPCHK_SEND_BINARY:
3703 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3704 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3705 goto error;
3706 }
3707 break;
3708 case TCPCHK_SEND_STRING_LF:
3709 case TCPCHK_SEND_BINARY_LF:
3710 LIST_INIT(&chk->send.fmt);
3711 px->conf.args.ctx = ARGC_SRV;
3712 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3713 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3714 goto error;
3715 }
3716 break;
3717 case TCPCHK_SEND_HTTP:
3718 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003719 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003720 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003721
Christopher Faulet61cc8522020-04-20 14:54:42 +02003722 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003723
Christopher Faulet61cc8522020-04-20 14:54:42 +02003724 error:
3725 free(chk);
3726 free(comment);
3727 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003728}
3729
Christopher Faulet61cc8522020-04-20 14:54:42 +02003730/* Parses and creates a http-check send rule. NULL is returned on error */
3731static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3732 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003733{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003734 struct tcpcheck_rule *chk = NULL;
3735 struct tcpcheck_http_hdr *hdr = NULL;
3736 struct http_hdr hdrs[global.tune.max_http_hdr];
3737 char *meth = NULL, *uri = NULL, *vsn = NULL;
3738 char *body = NULL, *comment = NULL;
3739 unsigned int flags = 0;
3740 int i = 0;
3741
3742 cur_arg++;
3743 while (*(args[cur_arg])) {
3744 if (strcmp(args[cur_arg], "meth") == 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 meth = args[cur_arg];
3751 }
3752 else if (strcmp(args[cur_arg], "uri") == 0) {
3753 if (!*(args[cur_arg+1])) {
3754 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3755 goto error;
3756 }
3757 cur_arg++;
3758 uri = args[cur_arg];
3759 // TODO: log-format uri
3760 }
3761 else if (strcmp(args[cur_arg], "vsn") == 0) {
3762 if (!*(args[cur_arg+1])) {
3763 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3764 goto error;
3765 }
3766 cur_arg++;
3767 vsn = args[cur_arg];
3768 }
3769 else if (strcmp(args[cur_arg], "hdr") == 0) {
3770 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3771 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3772 goto error;
3773 }
3774 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3775 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3776 i++;
3777 cur_arg += 2;
3778 }
3779 else if (strcmp(args[cur_arg], "body") == 0) {
3780 if (!*(args[cur_arg+1])) {
3781 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3782 goto error;
3783 }
3784 cur_arg++;
3785 body = args[cur_arg];
3786 // TODO: log-format body
3787 }
3788 else if (strcmp(args[cur_arg], "comment") == 0) {
3789 if (!*(args[cur_arg+1])) {
3790 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3791 goto error;
3792 }
3793 cur_arg++;
3794 free(comment);
3795 comment = strdup(args[cur_arg]);
3796 if (!comment) {
3797 memprintf(errmsg, "out of memory");
3798 goto error;
3799 }
3800 }
3801 else {
3802 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
3803 args[cur_arg]);
3804 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003805 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003806 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003807 }
3808
Christopher Faulet61cc8522020-04-20 14:54:42 +02003809 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810
Christopher Faulet61cc8522020-04-20 14:54:42 +02003811 chk = calloc(1, sizeof(*chk));
3812 if (!chk) {
3813 memprintf(errmsg, "out of memory");
3814 goto error;
3815 }
3816 chk->action = TCPCHK_ACT_SEND;
3817 chk->comment = comment; comment = NULL;
3818 chk->send.type = TCPCHK_SEND_HTTP;
3819 chk->send.http.flags = flags;
3820 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003821
Christopher Faulet61cc8522020-04-20 14:54:42 +02003822 if (meth) {
3823 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3824 chk->send.http.meth.str.area = strdup(meth);
3825 chk->send.http.meth.str.data = strlen(meth);
3826 if (!chk->send.http.meth.str.area) {
3827 memprintf(errmsg, "out of memory");
3828 goto error;
3829 }
3830 }
3831 if (uri) {
3832 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3833 if (!isttest(chk->send.http.uri)) {
3834 memprintf(errmsg, "out of memory");
3835 goto error;
3836 }
3837 }
3838 if (vsn) {
3839 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3840 if (!isttest(chk->send.http.vsn)) {
3841 memprintf(errmsg, "out of memory");
3842 goto error;
3843 }
3844 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003845 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003846 hdr = calloc(1, sizeof(*hdr));
3847 if (!hdr) {
3848 memprintf(errmsg, "out of memory");
3849 goto error;
3850 }
3851 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003852 hdr->name = istdup(hdrs[i].n);
3853 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003854 memprintf(errmsg, "out of memory");
3855 goto error;
3856 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003857
Christopher Fauletb61caf42020-04-21 10:57:42 +02003858 ist0(hdrs[i].v);
3859 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 +02003860 goto error;
3861 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3862 hdr = NULL;
3863 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003864
Christopher Faulet61cc8522020-04-20 14:54:42 +02003865 if (body) {
3866 chk->send.http.body = ist2(strdup(body), strlen(body));
3867 if (!isttest(chk->send.http.body)) {
3868 memprintf(errmsg, "out of memory");
3869 goto error;
3870 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003871 }
3872
Christopher Faulet61cc8522020-04-20 14:54:42 +02003873 return chk;
3874
3875 error:
3876 free_tcpcheck_http_hdr(hdr);
3877 free_tcpcheck(chk, 0);
3878 free(comment);
3879 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003880}
3881
Christopher Faulet61cc8522020-04-20 14:54:42 +02003882/* Parses and creates a http-check comment rule. NULL is returned on error */
3883static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3884 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003885{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003886 struct tcpcheck_rule *chk = NULL;
3887 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003888
Christopher Faulet61cc8522020-04-20 14:54:42 +02003889 if (!*(args[cur_arg+1])) {
3890 memprintf(errmsg, "expects a string as argument");
3891 goto error;
3892 }
3893 cur_arg++;
3894 comment = strdup(args[cur_arg]);
3895 if (!comment) {
3896 memprintf(errmsg, "out of memory");
3897 goto error;
3898 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003899
Christopher Faulet61cc8522020-04-20 14:54:42 +02003900 chk = calloc(1, sizeof(*chk));
3901 if (!chk) {
3902 memprintf(errmsg, "out of memory");
3903 goto error;
3904 }
3905 chk->action = TCPCHK_ACT_COMMENT;
3906 chk->comment = comment;
3907 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003908
Christopher Faulet61cc8522020-04-20 14:54:42 +02003909 error:
3910 free(comment);
3911 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003912}
3913
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3915 * on error. <proto> is set to the right protocol flags (covered by the
3916 * TCPCHK_RULES_PROTO_CHK mask).
3917 */
3918static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3919 struct list *rules, unsigned int proto,
3920 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003921{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003922 struct tcpcheck_rule *prev_check, *chk = NULL;
3923 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003924 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003925 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3926 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3927 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3928 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3929 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003930 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003931
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003932 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003933 if (!*(args[cur_arg+1])) {
3934 memprintf(errmsg, "expects at least a matching pattern as arguments");
3935 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003936 }
3937
Christopher Faulet61cc8522020-04-20 14:54:42 +02003938 cur_arg++;
3939 while (*(args[cur_arg])) {
3940 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003941
Christopher Faulet61cc8522020-04-20 14:54:42 +02003942 rescan:
3943 if (strcmp(args[cur_arg], "min-recv") == 0) {
3944 if (in_pattern) {
3945 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3946 goto error;
3947 }
3948 if (!*(args[cur_arg+1])) {
3949 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3950 goto error;
3951 }
3952 /* Use an signed integer here because of chksize */
3953 cur_arg++;
3954 min_recv = atol(args[cur_arg]);
3955 if (min_recv < -1 || min_recv > INT_MAX) {
3956 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3957 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003958 }
3959 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003960 else if (*(args[cur_arg]) == '!') {
3961 in_pattern = 1;
3962 while (*(args[cur_arg]) == '!') {
3963 inverse = !inverse;
3964 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003965 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003966 if (!*(args[cur_arg]))
3967 cur_arg++;
3968 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003969 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003970 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3971 if (type != TCPCHK_EXPECT_UNDEF) {
3972 memprintf(errmsg, "only on pattern expected");
3973 goto error;
3974 }
3975 if (proto != TCPCHK_RULES_HTTP_CHK)
3976 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3977 else
3978 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003979
Christopher Faulet61cc8522020-04-20 14:54:42 +02003980 if (!*(args[cur_arg+1])) {
3981 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3982 goto error;
3983 }
3984 cur_arg++;
3985 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003986 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003987 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3988 if (proto == TCPCHK_RULES_HTTP_CHK)
3989 goto bad_http_kw;
3990 if (type != TCPCHK_EXPECT_UNDEF) {
3991 memprintf(errmsg, "only on pattern expected");
3992 goto error;
3993 }
3994 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003995
Christopher Faulet61cc8522020-04-20 14:54:42 +02003996 if (!*(args[cur_arg+1])) {
3997 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3998 goto error;
3999 }
4000 cur_arg++;
4001 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004002 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004003 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4004 if (proto != TCPCHK_RULES_HTTP_CHK)
4005 goto bad_tcp_kw;
4006 if (type != TCPCHK_EXPECT_UNDEF) {
4007 memprintf(errmsg, "only on pattern expected");
4008 goto error;
4009 }
4010 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004011
Christopher Faulet61cc8522020-04-20 14:54:42 +02004012 if (!*(args[cur_arg+1])) {
4013 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4014 goto error;
4015 }
4016 cur_arg++;
4017 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004018 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004019 else if (strcmp(args[cur_arg], "custom") == 0) {
4020 if (in_pattern) {
4021 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4022 goto error;
4023 }
4024 if (type != TCPCHK_EXPECT_UNDEF) {
4025 memprintf(errmsg, "only on pattern expected");
4026 goto error;
4027 }
4028 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004029 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004030 else if (strcmp(args[cur_arg], "comment") == 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(comment);
4041 comment = strdup(args[cur_arg]);
4042 if (!comment) {
4043 memprintf(errmsg, "out of memory");
4044 goto error;
4045 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004046 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004047 else if (strcmp(args[cur_arg], "on-success") == 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_success_msg);
4058 on_success_msg = strdup(args[cur_arg]);
4059 if (!on_success_msg) {
4060 memprintf(errmsg, "out of memory");
4061 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004062 }
4063 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004064 else if (strcmp(args[cur_arg], "on-error") == 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 cur_arg++;
4074 free(on_error_msg);
4075 on_error_msg = strdup(args[cur_arg]);
4076 if (!on_error_msg) {
4077 memprintf(errmsg, "out of memory");
4078 goto error;
4079 }
4080 }
4081 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4082 if (in_pattern) {
4083 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4084 goto error;
4085 }
4086 if (!*(args[cur_arg+1])) {
4087 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4088 goto error;
4089 }
4090 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4091 ok_st = HCHK_STATUS_L7OKD;
4092 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4093 ok_st = HCHK_STATUS_L7OKCD;
4094 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4095 ok_st = HCHK_STATUS_L6OK;
4096 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4097 ok_st = HCHK_STATUS_L4OK;
4098 else {
4099 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4100 args[cur_arg], args[cur_arg+1]);
4101 goto error;
4102 }
4103 cur_arg++;
4104 }
4105 else if (strcmp(args[cur_arg], "error-status") == 0) {
4106 if (in_pattern) {
4107 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4108 goto error;
4109 }
4110 if (!*(args[cur_arg+1])) {
4111 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4112 goto error;
4113 }
4114 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4115 err_st = HCHK_STATUS_L7RSP;
4116 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4117 err_st = HCHK_STATUS_L7STS;
4118 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4119 err_st = HCHK_STATUS_L6RSP;
4120 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4121 err_st = HCHK_STATUS_L4CON;
4122 else {
4123 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4124 args[cur_arg], args[cur_arg+1]);
4125 goto error;
4126 }
4127 cur_arg++;
4128 }
4129 else if (strcmp(args[cur_arg], "status-code") == 0) {
4130 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004131
Christopher Faulet61cc8522020-04-20 14:54:42 +02004132 if (in_pattern) {
4133 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4134 goto error;
4135 }
4136 if (!*(args[cur_arg+1])) {
4137 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4138 goto error;
4139 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004140
Christopher Faulet61cc8522020-04-20 14:54:42 +02004141 cur_arg++;
4142 release_sample_expr(status_expr);
4143 px->conf.args.ctx = ARGC_SRV;
4144 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4145 file, line, errmsg, &px->conf.args, NULL);
4146 if (!status_expr) {
4147 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4148 goto error;
4149 }
4150 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4151 memprintf(errmsg, "error detected while parsing status-code expression : "
4152 " fetch method '%s' extracts information from '%s', "
4153 "none of which is available here.\n",
4154 args[cur_arg], sample_src_names(status_expr->fetch->use));
4155 goto error;
4156 }
4157 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4158 }
4159 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4160 if (in_pattern) {
4161 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4162 goto error;
4163 }
4164 if (!*(args[cur_arg+1])) {
4165 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4166 goto error;
4167 }
4168 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4169 tout_st = HCHK_STATUS_L7TOUT;
4170 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4171 tout_st = HCHK_STATUS_L6TOUT;
4172 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4173 tout_st = HCHK_STATUS_L4TOUT;
4174 else {
4175 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4176 args[cur_arg], args[cur_arg+1]);
4177 goto error;
4178 }
4179 cur_arg++;
4180 }
4181 else {
4182 if (proto == TCPCHK_RULES_HTTP_CHK) {
4183 bad_http_kw:
4184 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4185 " or comment but got '%s' as argument.", args[cur_arg]);
4186 }
4187 else {
4188 bad_tcp_kw:
4189 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4190 " or comment but got '%s' as argument.", args[cur_arg]);
4191 }
4192 goto error;
4193 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004194
Christopher Faulet61cc8522020-04-20 14:54:42 +02004195 cur_arg++;
4196 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004197
Christopher Faulet61cc8522020-04-20 14:54:42 +02004198 chk = calloc(1, sizeof(*chk));
4199 if (!chk) {
4200 memprintf(errmsg, "out of memory");
4201 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004202 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004203 chk->action = TCPCHK_ACT_EXPECT;
4204 LIST_INIT(&chk->expect.onerror_fmt);
4205 LIST_INIT(&chk->expect.onsuccess_fmt);
4206 chk->comment = comment; comment = NULL;
4207 chk->expect.type = type;
4208 chk->expect.min_recv = min_recv;
4209 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004210 chk->expect.ok_status = ok_st;
4211 chk->expect.err_status = err_st;
4212 chk->expect.tout_status = tout_st;
4213 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004214
Christopher Faulet61cc8522020-04-20 14:54:42 +02004215 if (on_success_msg) {
4216 px->conf.args.ctx = ARGC_SRV;
4217 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4218 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4219 goto error;
4220 }
4221 free(on_success_msg);
4222 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004223 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004224 if (on_error_msg) {
4225 px->conf.args.ctx = ARGC_SRV;
4226 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4227 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4228 goto error;
4229 }
4230 free(on_error_msg);
4231 on_error_msg = NULL;
4232 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004233
Christopher Faulet61cc8522020-04-20 14:54:42 +02004234 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004235 case TCPCHK_EXPECT_HTTP_STATUS: {
4236 const char *p = pattern;
4237 unsigned int c1,c2;
4238
4239 chk->expect.codes.codes = NULL;
4240 chk->expect.codes.num = 0;
4241 while (1) {
4242 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4243 if (*p == '-') {
4244 p++;
4245 c2 = read_uint(&p, pattern + strlen(pattern));
4246 }
4247 if (c1 > c2) {
4248 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4249 goto error;
4250 }
4251
4252 chk->expect.codes.num++;
4253 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4254 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4255 if (!chk->expect.codes.codes) {
4256 memprintf(errmsg, "out of memory");
4257 goto error;
4258 }
4259 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4260 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4261
4262 if (*p == '\0')
4263 break;
4264 if (*p != ',') {
4265 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4266 goto error;
4267 }
4268 p++;
4269 }
4270 break;
4271 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004272 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004273 case TCPCHK_EXPECT_HTTP_BODY:
4274 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004275 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004276 memprintf(errmsg, "out of memory");
4277 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004278 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004279 break;
4280 case TCPCHK_EXPECT_BINARY:
4281 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4282 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4283 goto error;
4284 }
4285 case TCPCHK_EXPECT_REGEX:
4286 case TCPCHK_EXPECT_REGEX_BINARY:
4287 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4288 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004289 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004290 if (!chk->expect.regex)
4291 goto error;
4292 break;
4293 case TCPCHK_EXPECT_CUSTOM:
4294 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4295 break;
4296 case TCPCHK_EXPECT_UNDEF:
4297 free(chk);
4298 memprintf(errmsg, "pattern not found");
4299 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004300 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004301
Christopher Faulet61cc8522020-04-20 14:54:42 +02004302 /* All tcp-check expect points back to the first inverse expect rule in
4303 * a chain of one or more expect rule, potentially itself.
4304 */
4305 chk->expect.head = chk;
4306 list_for_each_entry_rev(prev_check, rules, list) {
4307 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4308 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4309 chk->expect.head = prev_check;
4310 continue;
4311 }
4312 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4313 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004314 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004315 return chk;
4316
4317 error:
4318 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004319 free(comment);
4320 free(on_success_msg);
4321 free(on_error_msg);
4322 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004323 return NULL;
4324}
4325
Christopher Faulet61cc8522020-04-20 14:54:42 +02004326/* Overwrites fields of the old http send rule with those of the new one. When
4327 * replaced, old values are freed and replaced by the new ones. New values are
4328 * not copied but transferred. At the end <new> should be empty and can be
4329 * safely released. This function never fails.
4330 */
4331static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004332{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004333 struct logformat_node *lf, *lfb;
4334 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004335
Christopher Faulet404f9192020-04-09 23:13:54 +02004336
Christopher Faulet61cc8522020-04-20 14:54:42 +02004337 if (new->send.http.meth.str.area) {
4338 free(old->send.http.meth.str.area);
4339 old->send.http.meth.meth = new->send.http.meth.meth;
4340 old->send.http.meth.str.area = new->send.http.meth.str.area;
4341 old->send.http.meth.str.data = new->send.http.meth.str.data;
4342 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004343 }
4344
Christopher Faulet61cc8522020-04-20 14:54:42 +02004345 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4346 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004347 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004348 else
4349 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4350 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4351 old->send.http.uri = new->send.http.uri;
4352 new->send.http.uri = IST_NULL;
4353 }
4354 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4355 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004356 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004357 else
4358 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4359 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4360 LIST_INIT(&old->send.http.uri_fmt);
4361 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4362 LIST_DEL(&lf->list);
4363 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4364 }
4365 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004366
Christopher Faulet61cc8522020-04-20 14:54:42 +02004367 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004368 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004369 old->send.http.vsn = new->send.http.vsn;
4370 new->send.http.vsn = IST_NULL;
4371 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004372
Christopher Faulet61cc8522020-04-20 14:54:42 +02004373 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4374 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4375 LIST_DEL(&hdr->list);
4376 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004377 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004378
4379 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4380 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004381 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004382 else
4383 free_tcpcheck_fmt(&old->send.http.body_fmt);
4384 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4385 old->send.http.body = new->send.http.body;
4386 new->send.http.body = IST_NULL;
4387 }
4388 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4389 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004390 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004391 else
4392 free_tcpcheck_fmt(&old->send.http.body_fmt);
4393 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4394 LIST_INIT(&old->send.http.body_fmt);
4395 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4396 LIST_DEL(&lf->list);
4397 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4398 }
4399 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004400}
4401
Christopher Faulet61cc8522020-04-20 14:54:42 +02004402/* Internal function used to add an http-check rule in a list during the config
4403 * parsing step. Depending on its type, and the previously inserted rules, a
4404 * specific action may be performed or an error may be reported. This functions
4405 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4406 * message.
4407 */
4408static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004409{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004410 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004411
Christopher Faulet61cc8522020-04-20 14:54:42 +02004412 /* the implicit send rule coming from an "option httpchk" line must be
4413 * merged with the first explici http-check send rule, if
4414 * any. Depdending the declaration order some tests are required.
4415 *
4416 * Some tests is also required for other kinds of http-check rules to be
4417 * sure the ruleset remains valid.
4418 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004419
Christopher Faulet61cc8522020-04-20 14:54:42 +02004420 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4421 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4422 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4423 * following tests are performed :
4424 *
4425 * 1- If there is no such rule or if it is not a send rule, the implicit send
4426 * rule is pushed in front of the ruleset
4427 *
4428 * 2- If it is another implicit send rule, it is replaced with the new one.
4429 *
4430 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4431 * both, overwritting the old send rule (the explicit one) with info of the
4432 * new send rule (the implicit one).
4433 */
4434 r = get_first_tcpcheck_rule(rules);
4435 if (r && r->action == TCPCHK_ACT_CONNECT)
4436 r = get_next_tcpcheck_rule(rules, r);
4437 if (!r || r->action != TCPCHK_ACT_SEND)
4438 LIST_ADD(rules->list, &chk->list);
4439 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4440 LIST_DEL(&r->list);
4441 free_tcpcheck(r, 0);
4442 LIST_ADD(rules->list, &chk->list);
4443 }
4444 else {
4445 tcpcheck_overwrite_send_http_rule(r, chk);
4446 free_tcpcheck(chk, 0);
4447 }
4448 }
4449 else {
4450 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4451 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4452 * with an existing implicit send rule, if any. At the end, if there is no error,
4453 * the rule is appended to the list.
4454 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004455
Christopher Faulet61cc8522020-04-20 14:54:42 +02004456 r = get_last_tcpcheck_rule(rules);
4457 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4458 /* no error */;
4459 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4460 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4461 chk->index+1);
4462 return 0;
4463 }
4464 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4465 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4466 chk->index+1);
4467 return 0;
4468 }
4469 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4470 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4471 chk->index+1);
4472 return 0;
4473 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004474
Christopher Faulet61cc8522020-04-20 14:54:42 +02004475 if (chk->action == TCPCHK_ACT_SEND) {
4476 r = get_first_tcpcheck_rule(rules);
4477 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4478 tcpcheck_overwrite_send_http_rule(r, chk);
4479 free_tcpcheck(chk, 0);
4480 LIST_DEL(&r->list);
4481 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4482 chk = r;
4483 }
4484 }
4485 LIST_ADDQ(rules->list, &chk->list);
4486 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004487 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004488}
4489
Christopher Faulet61cc8522020-04-20 14:54:42 +02004490/**************************************************************************/
4491/************************** Init/deinit checks ****************************/
4492/**************************************************************************/
4493static const char *init_check(struct check *check, int type)
4494{
4495 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004496
Christopher Faulet61cc8522020-04-20 14:54:42 +02004497 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4498 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004499
Christopher Faulet61cc8522020-04-20 14:54:42 +02004500 check->bi.area = calloc(check->bi.size, sizeof(char));
4501 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004502
Christopher Faulet61cc8522020-04-20 14:54:42 +02004503 if (!check->bi.area || !check->bo.area)
4504 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004505
Christopher Faulet61cc8522020-04-20 14:54:42 +02004506 check->wait_list.tasklet = tasklet_new();
4507 if (!check->wait_list.tasklet)
4508 return "out of memory while allocating check tasklet";
4509 check->wait_list.events = 0;
4510 check->wait_list.tasklet->process = event_srv_chk_io;
4511 check->wait_list.tasklet->context = check;
4512 return NULL;
4513}
4514
4515void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004516{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004517 task_destroy(check->task);
4518 if (check->wait_list.tasklet)
4519 tasklet_free(check->wait_list.tasklet);
4520
4521 free(check->bi.area);
4522 free(check->bo.area);
4523 if (check->cs) {
4524 free(check->cs->conn);
4525 check->cs->conn = NULL;
4526 cs_free(check->cs);
4527 check->cs = NULL;
4528 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004529}
4530
Christopher Faulet61cc8522020-04-20 14:54:42 +02004531/* manages a server health-check. Returns the time the task accepts to wait, or
4532 * TIME_ETERNITY for infinity.
4533 */
4534static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004535{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004536 struct check *check = context;
4537
4538 if (check->type == PR_O2_EXT_CHK)
4539 return process_chk_proc(t, context, state);
4540 return process_chk_conn(t, context, state);
4541
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004542}
4543
Christopher Faulet61cc8522020-04-20 14:54:42 +02004544
4545static int start_check_task(struct check *check, int mininter,
4546 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004547{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004548 struct task *t;
4549 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004550
Christopher Faulet61cc8522020-04-20 14:54:42 +02004551 if (check->type == PR_O2_EXT_CHK)
4552 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004553
Christopher Faulet61cc8522020-04-20 14:54:42 +02004554 /* task for the check */
4555 if ((t = task_new(thread_mask)) == NULL) {
4556 ha_alert("Starting [%s:%s] check: out of memory.\n",
4557 check->server->proxy->id, check->server->id);
4558 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004559 }
4560
Christopher Faulet61cc8522020-04-20 14:54:42 +02004561 check->task = t;
4562 t->process = process_chk;
4563 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004564
Christopher Faulet61cc8522020-04-20 14:54:42 +02004565 if (mininter < srv_getinter(check))
4566 mininter = srv_getinter(check);
4567
4568 if (global.max_spread_checks && mininter > global.max_spread_checks)
4569 mininter = global.max_spread_checks;
4570
4571 /* check this every ms */
4572 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4573 check->start = now;
4574 task_queue(t);
4575
4576 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004577}
4578
Christopher Faulet61cc8522020-04-20 14:54:42 +02004579/* updates the server's weight during a warmup stage. Once the final weight is
4580 * reached, the task automatically stops. Note that any server status change
4581 * must have updated s->last_change accordingly.
4582 */
4583static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004584{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004585 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004586
Christopher Faulet61cc8522020-04-20 14:54:42 +02004587 /* by default, plan on stopping the task */
4588 t->expire = TICK_ETERNITY;
4589 if ((s->next_admin & SRV_ADMF_MAINT) ||
4590 (s->next_state != SRV_ST_STARTING))
4591 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004592
Christopher Faulet61cc8522020-04-20 14:54:42 +02004593 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004594
Christopher Faulet61cc8522020-04-20 14:54:42 +02004595 /* recalculate the weights and update the state */
4596 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004597
Christopher Faulet61cc8522020-04-20 14:54:42 +02004598 /* probably that we can refill this server with a bit more connections */
4599 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004600
Christopher Faulet61cc8522020-04-20 14:54:42 +02004601 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004602
Christopher Faulet61cc8522020-04-20 14:54:42 +02004603 /* get back there in 1 second or 1/20th of the slowstart interval,
4604 * whichever is greater, resulting in small 5% steps.
4605 */
4606 if (s->next_state == SRV_ST_STARTING)
4607 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4608 return t;
4609}
4610
4611/*
4612 * Start health-check.
4613 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4614 */
4615static int start_checks()
4616{
4617
4618 struct proxy *px;
4619 struct server *s;
4620 struct task *t;
4621 int nbcheck=0, mininter=0, srvpos=0;
4622
4623 /* 0- init the dummy frontend used to create all checks sessions */
4624 init_new_proxy(&checks_fe);
4625 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4626 checks_fe.mode = PR_MODE_TCP;
4627 checks_fe.maxconn = 0;
4628 checks_fe.conn_retries = CONN_RETRIES;
4629 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4630 checks_fe.timeout.client = TICK_ETERNITY;
4631
4632 /* 1- count the checkers to run simultaneously.
4633 * We also determine the minimum interval among all of those which
4634 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4635 * will be used to spread their start-up date. Those which have
4636 * a shorter interval will start independently and will not dictate
4637 * too short an interval for all others.
4638 */
4639 for (px = proxies_list; px; px = px->next) {
4640 for (s = px->srv; s; s = s->next) {
4641 if (s->slowstart) {
4642 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4643 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4644 return ERR_ALERT | ERR_FATAL;
4645 }
4646 /* We need a warmup task that will be called when the server
4647 * state switches from down to up.
4648 */
4649 s->warmup = t;
4650 t->process = server_warmup;
4651 t->context = s;
4652 /* server can be in this state only because of */
4653 if (s->next_state == SRV_ST_STARTING)
4654 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 +02004655 }
4656
Christopher Faulet61cc8522020-04-20 14:54:42 +02004657 if (s->check.state & CHK_ST_CONFIGURED) {
4658 nbcheck++;
4659 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4660 (!mininter || mininter > srv_getinter(&s->check)))
4661 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004662 }
4663
Christopher Faulet61cc8522020-04-20 14:54:42 +02004664 if (s->agent.state & CHK_ST_CONFIGURED) {
4665 nbcheck++;
4666 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4667 (!mininter || mininter > srv_getinter(&s->agent)))
4668 mininter = srv_getinter(&s->agent);
4669 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004670 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004671 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004672
Christopher Faulet61cc8522020-04-20 14:54:42 +02004673 if (!nbcheck)
4674 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004675
Christopher Faulet61cc8522020-04-20 14:54:42 +02004676 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004677
Christopher Faulet61cc8522020-04-20 14:54:42 +02004678 /*
4679 * 2- start them as far as possible from each others. For this, we will
4680 * start them after their interval set to the min interval divided by
4681 * the number of servers, weighted by the server's position in the list.
4682 */
4683 for (px = proxies_list; px; px = px->next) {
4684 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4685 if (init_pid_list()) {
4686 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4687 return ERR_ALERT | ERR_FATAL;
4688 }
4689 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004690
Christopher Faulet61cc8522020-04-20 14:54:42 +02004691 for (s = px->srv; s; s = s->next) {
4692 /* A task for the main check */
4693 if (s->check.state & CHK_ST_CONFIGURED) {
4694 if (s->check.type == PR_O2_EXT_CHK) {
4695 if (!prepare_external_check(&s->check))
4696 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004697 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004698 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4699 return ERR_ALERT | ERR_FATAL;
4700 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004701 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004702
Christopher Faulet61cc8522020-04-20 14:54:42 +02004703 /* A task for a auxiliary agent check */
4704 if (s->agent.state & CHK_ST_CONFIGURED) {
4705 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4706 return ERR_ALERT | ERR_FATAL;
4707 }
4708 srvpos++;
4709 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004710 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004711 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004712 return 0;
4713}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004714
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004715
Christopher Faulet61cc8522020-04-20 14:54:42 +02004716/*
4717 * Return value:
4718 * the port to be used for the health check
4719 * 0 in case no port could be found for the check
4720 */
4721static int srv_check_healthcheck_port(struct check *chk)
4722{
4723 int i = 0;
4724 struct server *srv = NULL;
4725
4726 srv = chk->server;
4727
4728 /* by default, we use the health check port ocnfigured */
4729 if (chk->port > 0)
4730 return chk->port;
4731
4732 /* try to get the port from check_core.addr if check.port not set */
4733 i = get_host_port(&chk->addr);
4734 if (i > 0)
4735 return i;
4736
4737 /* try to get the port from server address */
4738 /* prevent MAPPORTS from working at this point, since checks could
4739 * not be performed in such case (MAPPORTS impose a relative ports
4740 * based on live traffic)
4741 */
4742 if (srv->flags & SRV_F_MAPPORTS)
4743 return 0;
4744
4745 i = srv->svc_port; /* by default */
4746 if (i > 0)
4747 return i;
4748
4749 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004750}
4751
Christopher Faulet61cc8522020-04-20 14:54:42 +02004752/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4753 * if an error occurred.
4754 */
4755static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004756{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004757 const char *err;
4758 struct tcpcheck_rule *r;
4759 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004760
Christopher Faulet61cc8522020-04-20 14:54:42 +02004761 if (!srv->do_check)
4762 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004763
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004764
Christopher Faulet61cc8522020-04-20 14:54:42 +02004765 /* If neither a port nor an addr was specified and no check transport
4766 * layer is forced, then the transport layer used by the checks is the
4767 * same as for the production traffic. Otherwise we use raw_sock by
4768 * default, unless one is specified.
4769 */
4770 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4771 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4772 srv->check.use_ssl = srv->use_ssl;
4773 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004774 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004775 else if (srv->check.use_ssl == 1)
4776 srv->check.xprt = xprt_get(XPRT_SSL);
4777 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004778 }
4779
Christopher Faulet12882cf2020-04-23 15:50:18 +02004780 /* Inherit the mux protocol from the server if not already defined for
4781 * the check
4782 */
4783 if (srv->mux_proto && !srv->check.mux_proto)
4784 srv->check.mux_proto = srv->mux_proto;
4785
Christopher Faulet61cc8522020-04-20 14:54:42 +02004786 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004787
Christopher Faulet61cc8522020-04-20 14:54:42 +02004788 /* We need at least a service port, a check port or the first tcp-check
4789 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4790 */
4791 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4792 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4793 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004794
Christopher Faulet61cc8522020-04-20 14:54:42 +02004795 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4796 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4797 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4798 ret |= ERR_ALERT | ERR_ABORT;
4799 goto out;
4800 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004801
Christopher Faulet61cc8522020-04-20 14:54:42 +02004802 /* search the first action (connect / send / expect) in the list */
4803 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4804 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4805 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4806 "nor tcp_check rule 'connect' with port information.\n",
4807 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4808 ret |= ERR_ALERT | ERR_ABORT;
4809 goto out;
4810 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004811
Christopher Faulet61cc8522020-04-20 14:54:42 +02004812 /* scan the tcp-check ruleset to ensure a port has been configured */
4813 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4814 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4815 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4816 "and a tcp_check rule 'connect' with no port information.\n",
4817 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4818 ret |= ERR_ALERT | ERR_ABORT;
4819 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004820 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004821 }
4822
Christopher Faulet61cc8522020-04-20 14:54:42 +02004823 init:
4824 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4825 struct tcpcheck_ruleset *rs = NULL;
4826 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4827 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004828
Christopher Faulet61cc8522020-04-20 14:54:42 +02004829 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4830 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004831
Christopher Faulet61cc8522020-04-20 14:54:42 +02004832 rs = find_tcpcheck_ruleset("*tcp-check");
4833 if (!rs) {
4834 rs = create_tcpcheck_ruleset("*tcp-check");
4835 if (rs == NULL) {
4836 ha_alert("config: %s '%s': out of memory.\n",
4837 proxy_type_str(srv->proxy), srv->proxy->id);
4838 ret |= ERR_ALERT | ERR_FATAL;
4839 goto out;
4840 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004841 }
4842
Christopher Faulet61cc8522020-04-20 14:54:42 +02004843 free_tcpcheck_vars(&rules->preset_vars);
4844 rules->list = &rs->rules;
4845 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004846 }
4847
Christopher Faulet61cc8522020-04-20 14:54:42 +02004848 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4849 if (err) {
4850 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4851 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4852 ret |= ERR_ALERT | ERR_ABORT;
4853 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004854 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004855 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4856 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004857
Christopher Faulet61cc8522020-04-20 14:54:42 +02004858 out:
4859 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004860}
4861
Christopher Faulet61cc8522020-04-20 14:54:42 +02004862/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4863 * if an error occurred.
4864 */
4865static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004866{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004867 struct tcpcheck_rule *chk;
4868 const char *err;
4869 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004870
Christopher Faulet61cc8522020-04-20 14:54:42 +02004871 if (!srv->do_agent)
4872 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004873
Christopher Faulet61cc8522020-04-20 14:54:42 +02004874 /* If there is no connect rule preceeding all send / expect rules, an
4875 * implicit one is inserted before all others.
4876 */
4877 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4878 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4879 chk = calloc(1, sizeof(*chk));
4880 if (!chk) {
4881 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4882 " to agent-check for server '%s' (out of memory).\n",
4883 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4884 ret |= ERR_ALERT | ERR_FATAL;
4885 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004886 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004887 chk->action = TCPCHK_ACT_CONNECT;
4888 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4889 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004890 }
4891
Christopher Faulete5870d82020-04-15 11:32:03 +02004892
Christopher Faulet61cc8522020-04-20 14:54:42 +02004893 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4894 if (err) {
4895 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4896 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4897 ret |= ERR_ALERT | ERR_ABORT;
4898 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004899 }
4900
Christopher Faulet61cc8522020-04-20 14:54:42 +02004901 if (!srv->agent.inter)
4902 srv->agent.inter = srv->check.inter;
4903
4904 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4905 global.maxsock++;
4906
4907 out:
4908 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004909}
4910
Christopher Faulet61cc8522020-04-20 14:54:42 +02004911/* Check tcp-check health-check configuration for the proxy <px>. */
4912static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004913{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004914 struct tcpcheck_rule *chk, *back;
4915 char *comment = NULL, *errmsg = NULL;
4916 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4917 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004918
Christopher Faulet61cc8522020-04-20 14:54:42 +02004919 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4920 deinit_proxy_tcpcheck(px);
4921 goto out;
4922 }
4923
4924 free(px->check_command);
4925 free(px->check_path);
4926 px->check_command = px->check_path = NULL;
4927
4928 if (!px->tcpcheck_rules.list) {
4929 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4930 ret |= ERR_ALERT | ERR_FATAL;
4931 goto out;
4932 }
4933
4934 /* HTTP ruleset only : */
4935 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4936 struct tcpcheck_rule *next;
4937
4938 /* move remaining implicit send rule from "option httpchk" line to the right place.
4939 * If such rule exists, it must be the first one. In this case, the rule is moved
4940 * after the first connect rule, if any. Otherwise, nothing is done.
4941 */
4942 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4943 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4944 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4945 if (next && next->action == TCPCHK_ACT_CONNECT) {
4946 LIST_DEL(&chk->list);
4947 LIST_ADD(&next->list, &chk->list);
4948 chk->index = next->index;
4949 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004950 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004951
4952 /* add implicit expect rule if the last one is a send. It is inherited from previous
4953 * versions where the http expect rule was optional. Now it is possible to chained
4954 * send/expect rules but the last expect may still be implicit.
4955 */
4956 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4957 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004958 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004959 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4960 px->conf.file, px->conf.line, &errmsg);
4961 if (!next) {
4962 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4963 "(%s).\n", px->id, errmsg);
4964 free(errmsg);
4965 ret |= ERR_ALERT | ERR_FATAL;
4966 goto out;
4967 }
4968 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4969 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004970 }
4971 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004972
4973 /* For all ruleset: */
4974
4975 /* If there is no connect rule preceeding all send / expect rules, an
4976 * implicit one is inserted before all others.
4977 */
4978 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4979 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4980 chk = calloc(1, sizeof(*chk));
4981 if (!chk) {
4982 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4983 "(out of memory).\n", px->id);
4984 ret |= ERR_ALERT | ERR_FATAL;
4985 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004986 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004987 chk->action = TCPCHK_ACT_CONNECT;
4988 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4989 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4990 }
4991
4992 /* Remove all comment rules. To do so, when a such rule is found, the
4993 * comment is assigned to the following rule(s).
4994 */
4995 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4996 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4997 free(comment);
4998 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004999 }
5000
Christopher Faulet61cc8522020-04-20 14:54:42 +02005001 prev_action = chk->action;
5002 switch (chk->action) {
5003 case TCPCHK_ACT_COMMENT:
5004 free(comment);
5005 comment = chk->comment;
5006 LIST_DEL(&chk->list);
5007 free(chk);
5008 break;
5009 case TCPCHK_ACT_CONNECT:
5010 if (!chk->comment && comment)
5011 chk->comment = strdup(comment);
5012 /* fall though */
5013 case TCPCHK_ACT_ACTION_KW:
5014 free(comment);
5015 comment = NULL;
5016 break;
5017 case TCPCHK_ACT_SEND:
5018 case TCPCHK_ACT_EXPECT:
5019 if (!chk->comment && comment)
5020 chk->comment = strdup(comment);
5021 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005022 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005023 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005024 free(comment);
5025 comment = NULL;
5026
5027 out:
5028 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005029}
5030
Christopher Faulet61cc8522020-04-20 14:54:42 +02005031void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005032{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5034 px->tcpcheck_rules.flags = 0;
5035 px->tcpcheck_rules.list = NULL;
5036}
Christopher Faulete5870d82020-04-15 11:32:03 +02005037
Christopher Faulet61cc8522020-04-20 14:54:42 +02005038static void deinit_srv_check(struct server *srv)
5039{
5040 if (srv->check.state & CHK_ST_CONFIGURED)
5041 free_check(&srv->check);
5042 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5043 srv->do_check = 0;
5044}
Christopher Faulete5870d82020-04-15 11:32:03 +02005045
Christopher Faulet61cc8522020-04-20 14:54:42 +02005046
5047static void deinit_srv_agent_check(struct server *srv)
5048{
5049 if (srv->agent.tcpcheck_rules) {
5050 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5051 free(srv->agent.tcpcheck_rules);
5052 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005053 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005054
Christopher Faulet61cc8522020-04-20 14:54:42 +02005055 if (srv->agent.state & CHK_ST_CONFIGURED)
5056 free_check(&srv->agent);
5057
5058 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5059 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005060}
5061
Christopher Faulet61cc8522020-04-20 14:54:42 +02005062static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005063{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005064 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005065 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005066 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005067
Christopher Fauletd7cee712020-04-21 13:45:00 +02005068 node = ebpt_first(&shared_tcpchecks);
5069 while (node) {
5070 next = ebpt_next(node);
5071 ebpt_delete(node);
5072 free(node->key);
5073 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005074 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5075 LIST_DEL(&r->list);
5076 free_tcpcheck(r, 0);
5077 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005078 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005079 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005080 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005081}
Christopher Faulete5870d82020-04-15 11:32:03 +02005082
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005083
Christopher Faulet61cc8522020-04-20 14:54:42 +02005084REGISTER_POST_SERVER_CHECK(init_srv_check);
5085REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5086REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5087REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005088
Christopher Faulet61cc8522020-04-20 14:54:42 +02005089REGISTER_SERVER_DEINIT(deinit_srv_check);
5090REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5091REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5092REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005093
Christopher Faulet61cc8522020-04-20 14:54:42 +02005094/**************************************************************************/
5095/****************************** Email alerts ******************************/
5096/* NOTE: It may be pertinent to use an applet to handle email alerts */
5097/* instead of a tcp-check ruleset */
5098/**************************************************************************/
5099void email_alert_free(struct email_alert *alert)
5100{
5101 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005102
Christopher Faulet61cc8522020-04-20 14:54:42 +02005103 if (!alert)
5104 return;
5105
5106 if (alert->rules.list) {
5107 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5108 LIST_DEL(&rule->list);
5109 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005110 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005111 free_tcpcheck_vars(&alert->rules.preset_vars);
5112 free(alert->rules.list);
5113 alert->rules.list = NULL;
5114 }
5115 pool_free(pool_head_email_alert, alert);
5116}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005117
Christopher Faulet61cc8522020-04-20 14:54:42 +02005118static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5119{
5120 struct check *check = context;
5121 struct email_alertq *q;
5122 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005123
Christopher Faulet61cc8522020-04-20 14:54:42 +02005124 q = container_of(check, typeof(*q), check);
5125
5126 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5127 while (1) {
5128 if (!(check->state & CHK_ST_ENABLED)) {
5129 if (LIST_ISEMPTY(&q->email_alerts)) {
5130 /* All alerts processed, queue the task */
5131 t->expire = TICK_ETERNITY;
5132 task_queue(t);
5133 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005134 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005135
5136 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5137 LIST_DEL(&alert->list);
5138 t->expire = now_ms;
5139 check->tcpcheck_rules = &alert->rules;
5140 check->status = HCHK_STATUS_INI;
5141 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005142 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005143
5144 process_chk(t, context, state);
5145 if (check->state & CHK_ST_INPROGRESS)
5146 break;
5147
5148 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5149 email_alert_free(alert);
5150 check->tcpcheck_rules = NULL;
5151 check->server = NULL;
5152 check->state &= ~CHK_ST_ENABLED;
5153 }
5154 end:
5155 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5156 return t;
5157}
5158
5159/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5160 *
5161 * The function returns 1 in success case, otherwise, it returns 0 and err is
5162 * filled.
5163 */
5164int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5165{
5166 struct mailer *mailer;
5167 struct email_alertq *queues;
5168 const char *err_str;
5169 int i = 0;
5170
5171 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5172 memprintf(err, "out of memory while allocating mailer alerts queues");
5173 goto fail_no_queue;
5174 }
5175
5176 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5177 struct email_alertq *q = &queues[i];
5178 struct check *check = &q->check;
5179 struct task *t;
5180
5181 LIST_INIT(&q->email_alerts);
5182 HA_SPIN_INIT(&q->lock);
5183 check->inter = mls->timeout.mail;
5184 check->rise = DEF_AGENT_RISETIME;
5185 check->proxy = p;
5186 check->fall = DEF_AGENT_FALLTIME;
5187 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5188 memprintf(err, "%s", err_str);
5189 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005190 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005191
5192 check->xprt = mailer->xprt;
5193 check->addr = mailer->addr;
5194 check->port = get_host_port(&mailer->addr);
5195
5196 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5197 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005198 goto error;
5199 }
5200
Christopher Faulet61cc8522020-04-20 14:54:42 +02005201 check->task = t;
5202 t->process = process_email_alert;
5203 t->context = check;
5204
5205 /* check this in one ms */
5206 t->expire = TICK_ETERNITY;
5207 check->start = now;
5208 task_queue(t);
5209 }
5210
5211 mls->users++;
5212 free(p->email_alert.mailers.name);
5213 p->email_alert.mailers.m = mls;
5214 p->email_alert.queues = queues;
5215 return 0;
5216
5217 error:
5218 for (i = 0; i < mls->count; i++) {
5219 struct email_alertq *q = &queues[i];
5220 struct check *check = &q->check;
5221
5222 free_check(check);
5223 }
5224 free(queues);
5225 fail_no_queue:
5226 return 1;
5227}
5228
5229static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5230{
5231 struct tcpcheck_rule *tcpcheck, *prev_check;
5232 struct tcpcheck_expect *expect;
5233
5234 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5235 return 0;
5236 memset(tcpcheck, 0, sizeof(*tcpcheck));
5237 tcpcheck->action = TCPCHK_ACT_EXPECT;
5238
5239 expect = &tcpcheck->expect;
5240 expect->type = TCPCHK_EXPECT_STRING;
5241 LIST_INIT(&expect->onerror_fmt);
5242 LIST_INIT(&expect->onsuccess_fmt);
5243 expect->ok_status = HCHK_STATUS_L7OKD;
5244 expect->err_status = HCHK_STATUS_L7RSP;
5245 expect->tout_status = HCHK_STATUS_L7TOUT;
5246 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005247 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005248 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5249 return 0;
5250 }
5251
5252 /* All tcp-check expect points back to the first inverse expect rule
5253 * in a chain of one or more expect rule, potentially itself.
5254 */
5255 tcpcheck->expect.head = tcpcheck;
5256 list_for_each_entry_rev(prev_check, rules->list, list) {
5257 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5258 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5259 tcpcheck->expect.head = prev_check;
5260 continue;
5261 }
5262 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5263 break;
5264 }
5265 LIST_ADDQ(rules->list, &tcpcheck->list);
5266 return 1;
5267}
5268
5269static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5270{
5271 struct tcpcheck_rule *tcpcheck;
5272 struct tcpcheck_send *send;
5273 const char *in;
5274 char *dst;
5275 int i;
5276
5277 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5278 return 0;
5279 memset(tcpcheck, 0, sizeof(*tcpcheck));
5280 tcpcheck->action = TCPCHK_ACT_SEND;
5281
5282 send = &tcpcheck->send;
5283 send->type = TCPCHK_SEND_STRING;
5284
5285 for (i = 0; strs[i]; i++)
5286 send->data.len += strlen(strs[i]);
5287
Christopher Fauletb61caf42020-04-21 10:57:42 +02005288 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005289 if (!isttest(send->data)) {
5290 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5291 return 0;
5292 }
5293
Christopher Fauletb61caf42020-04-21 10:57:42 +02005294 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005295 for (i = 0; strs[i]; i++)
5296 for (in = strs[i]; (*dst = *in++); dst++);
5297 *dst = 0;
5298
5299 LIST_ADDQ(rules->list, &tcpcheck->list);
5300 return 1;
5301}
5302
5303static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5304 struct email_alertq *q, const char *msg)
5305{
5306 struct email_alert *alert;
5307 struct tcpcheck_rule *tcpcheck;
5308 struct check *check = &q->check;
5309
5310 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5311 goto error;
5312 LIST_INIT(&alert->list);
5313 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5314 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5315 if (!alert->rules.list)
5316 goto error;
5317 LIST_INIT(alert->rules.list);
5318 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5319 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005320
Christopher Faulet61cc8522020-04-20 14:54:42 +02005321 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5322 goto error;
5323 memset(tcpcheck, 0, sizeof(*tcpcheck));
5324 tcpcheck->action = TCPCHK_ACT_CONNECT;
5325 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005326
Christopher Faulet61cc8522020-04-20 14:54:42 +02005327 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005328
Christopher Faulet61cc8522020-04-20 14:54:42 +02005329 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005330 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005331
5332 {
5333 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5334 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5335 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005336 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005337
Christopher Faulet61cc8522020-04-20 14:54:42 +02005338 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5339 goto error;
5340
5341 {
5342 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5343 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005344 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005345 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005346
5347 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5348 goto error;
5349
5350 {
5351 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5352 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005353 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005354 }
5355
Christopher Faulet61cc8522020-04-20 14:54:42 +02005356 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5357 goto error;
5358
5359 {
5360 const char * const strs[2] = { "DATA\r\n" };
5361 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005362 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005363 }
5364
5365 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5366 goto error;
5367
5368 {
5369 struct tm tm;
5370 char datestr[48];
5371 const char * const strs[18] = {
5372 "From: ", p->email_alert.from, "\r\n",
5373 "To: ", p->email_alert.to, "\r\n",
5374 "Date: ", datestr, "\r\n",
5375 "Subject: [HAproxy Alert] ", msg, "\r\n",
5376 "\r\n",
5377 msg, "\r\n",
5378 "\r\n",
5379 ".\r\n",
5380 NULL
5381 };
5382
5383 get_localtime(date.tv_sec, &tm);
5384
5385 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005386 goto error;
5387 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005388
5389 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005390 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005391 }
5392
5393 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005394 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005395
5396 {
5397 const char * const strs[2] = { "QUIT\r\n" };
5398 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5399 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005400 }
5401
Christopher Faulet61cc8522020-04-20 14:54:42 +02005402 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5403 goto error;
5404
5405 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5406 task_wakeup(check->task, TASK_WOKEN_MSG);
5407 LIST_ADDQ(&q->email_alerts, &alert->list);
5408 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5409 return 1;
5410
5411error:
5412 email_alert_free(alert);
5413 return 0;
5414}
5415
5416static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5417{
5418 int i;
5419 struct mailer *mailer;
5420
5421 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5422 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5423 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5424 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5425 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005426 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005427 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005428
Christopher Faulet61cc8522020-04-20 14:54:42 +02005429 return;
5430}
5431
5432/*
5433 * Send email alert if configured.
5434 */
5435void send_email_alert(struct server *s, int level, const char *format, ...)
5436{
5437 va_list argp;
5438 char buf[1024];
5439 int len;
5440 struct proxy *p = s->proxy;
5441
5442 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5443 return;
5444
5445 va_start(argp, format);
5446 len = vsnprintf(buf, sizeof(buf), format, argp);
5447 va_end(argp);
5448
5449 if (len < 0 || len >= sizeof(buf)) {
5450 ha_alert("Email alert [%s] could not format message\n", p->id);
5451 return;
5452 }
5453
5454 enqueue_email_alert(p, s, buf);
5455}
5456
5457/**************************************************************************/
5458/************************** Check sample fetches **************************/
5459/**************************************************************************/
5460/* extracts check payload at a fixed position and length */
5461static int
5462smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5463{
5464 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5465 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5466 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5467 struct buffer *buf;
5468
5469 if (!check)
5470 return 0;
5471
5472 buf = &check->bi;
5473 if (buf_offset > b_data(buf))
5474 goto no_match;
5475 if (buf_offset + buf_size > b_data(buf))
5476 buf_size = 0;
5477
5478 /* init chunk as read only */
5479 smp->data.type = SMP_T_STR;
5480 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5481 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5482
5483 return 1;
5484
5485 no_match:
5486 smp->flags = 0;
5487 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005488}
5489
Christopher Faulet61cc8522020-04-20 14:54:42 +02005490static struct sample_fetch_kw_list smp_kws = {ILH, {
5491 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5492 { /* END */ },
5493}};
5494
5495INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5496
5497
5498/**************************************************************************/
5499/************************ Check's parsing functions ***********************/
5500/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005501/* Parses the "tcp-check" proxy keyword */
5502static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5503 struct proxy *defpx, const char *file, int line,
5504 char **errmsg)
5505{
Christopher Faulet404f9192020-04-09 23:13:54 +02005506 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005507 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005508 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005509
5510 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5511 ret = 1;
5512
Christopher Faulet404f9192020-04-09 23:13:54 +02005513 /* Deduce the ruleset name from the proxy info */
5514 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5515 ((curpx == defpx) ? "defaults" : curpx->id),
5516 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005517
Christopher Faulet61cc8522020-04-20 14:54:42 +02005518 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005519 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005520 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005521 if (rs == NULL) {
5522 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005523 goto error;
5524 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005525 }
5526
Gaetan Rivet5301b012020-02-25 17:19:17 +01005527 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005528 if (!LIST_ISEMPTY(&rs->rules)) {
5529 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005530 index = chk->index + 1;
5531 }
5532
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005533 cur_arg = 1;
5534 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005535 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005536 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005537 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005538 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005539 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005540 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005541 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005542 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005543 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5544
5545 if (!kw) {
5546 action_kw_tcp_check_build_list(&trash);
5547 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5548 "%s%s. but got '%s'",
5549 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5550 goto error;
5551 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005552 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005553 }
5554
5555 if (!chk) {
5556 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5557 goto error;
5558 }
5559 ret = (*errmsg != NULL); /* Handle warning */
5560
5561 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005562 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005563 LIST_ADDQ(&rs->rules, &chk->list);
5564
5565 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005566 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005567 /* Use this ruleset if the proxy already has tcp-check enabled */
5568 curpx->tcpcheck_rules.list = &rs->rules;
5569 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5570 }
5571 else {
5572 /* mark this ruleset as unused for now */
5573 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5574 }
5575
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005576 return ret;
5577
5578 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005579 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005580 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005581 return -1;
5582}
5583
Christopher Faulet51b129f2020-04-09 15:54:18 +02005584/* Parses the "http-check" proxy keyword */
5585static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5586 struct proxy *defpx, const char *file, int line,
5587 char **errmsg)
5588{
Christopher Faulete5870d82020-04-15 11:32:03 +02005589 struct tcpcheck_ruleset *rs = NULL;
5590 struct tcpcheck_rule *chk = NULL;
5591 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005592
5593 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5594 ret = 1;
5595
5596 cur_arg = 1;
5597 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5598 /* enable a graceful server shutdown on an HTTP 404 response */
5599 curpx->options |= PR_O_DISABLE404;
5600 if (too_many_args(1, args, errmsg, NULL))
5601 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005602 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005603 }
5604 else if (strcmp(args[cur_arg], "send-state") == 0) {
5605 /* enable emission of the apparent state of a server in HTTP checks */
5606 curpx->options2 |= PR_O2_CHK_SNDST;
5607 if (too_many_args(1, args, errmsg, NULL))
5608 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005609 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005610 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005611
Christopher Faulete5870d82020-04-15 11:32:03 +02005612 /* Deduce the ruleset name from the proxy info */
5613 chunk_printf(&trash, "*http-check-%s_%s-%d",
5614 ((curpx == defpx) ? "defaults" : curpx->id),
5615 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005616
Christopher Faulet61cc8522020-04-20 14:54:42 +02005617 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005618 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005619 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005620 if (rs == NULL) {
5621 memprintf(errmsg, "out of memory.\n");
5622 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005623 }
5624 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005625
Christopher Faulete5870d82020-04-15 11:32:03 +02005626 index = 0;
5627 if (!LIST_ISEMPTY(&rs->rules)) {
5628 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5629 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5630 index = chk->index + 1;
5631 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005632
Christopher Faulete5870d82020-04-15 11:32:03 +02005633 if (strcmp(args[cur_arg], "connect") == 0)
5634 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5635 else if (strcmp(args[cur_arg], "send") == 0)
5636 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5637 else if (strcmp(args[cur_arg], "expect") == 0)
5638 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5639 file, line, errmsg);
5640 else if (strcmp(args[cur_arg], "comment") == 0)
5641 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5642 else {
5643 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005644
Christopher Faulete5870d82020-04-15 11:32:03 +02005645 if (!kw) {
5646 action_kw_tcp_check_build_list(&trash);
5647 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5648 " 'send', 'expect'%s%s. but got '%s'",
5649 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5650 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005651 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005652 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5653 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005654
Christopher Faulete5870d82020-04-15 11:32:03 +02005655 if (!chk) {
5656 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5657 goto error;
5658 }
5659 ret = (*errmsg != NULL); /* Handle warning */
5660
5661 chk->index = index;
5662 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5663 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5664 /* Use this ruleset if the proxy already has http-check enabled */
5665 curpx->tcpcheck_rules.list = &rs->rules;
5666 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5667 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5668 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5669 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005670 goto error;
5671 }
5672 }
5673 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005674 /* mark this ruleset as unused for now */
5675 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5676 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005677 }
5678
Christopher Faulete5870d82020-04-15 11:32:03 +02005679 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005680 return ret;
5681
5682 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005683 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005684 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005685 return -1;
5686}
5687
Christopher Faulete9111b62020-04-09 18:12:08 +02005688/* Parses the "external-check" proxy keyword */
5689static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5690 struct proxy *defpx, const char *file, int line,
5691 char **errmsg)
5692{
5693 int cur_arg, ret = 0;
5694
5695 cur_arg = 1;
5696 if (!*(args[cur_arg])) {
5697 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5698 goto error;
5699 }
5700
5701 if (strcmp(args[cur_arg], "command") == 0) {
5702 if (too_many_args(2, args, errmsg, NULL))
5703 goto error;
5704 if (!*(args[cur_arg+1])) {
5705 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5706 goto error;
5707 }
5708 free(curpx->check_command);
5709 curpx->check_command = strdup(args[cur_arg+1]);
5710 }
5711 else if (strcmp(args[cur_arg], "path") == 0) {
5712 if (too_many_args(2, args, errmsg, NULL))
5713 goto error;
5714 if (!*(args[cur_arg+1])) {
5715 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5716 goto error;
5717 }
5718 free(curpx->check_path);
5719 curpx->check_path = strdup(args[cur_arg+1]);
5720 }
5721 else {
5722 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5723 args[0], args[1]);
5724 goto error;
5725 }
5726
5727 ret = (*errmsg != NULL); /* Handle warning */
5728 return ret;
5729
5730error:
5731 return -1;
5732}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005733
Christopher Faulet430e4802020-04-09 15:28:16 +02005734/* Parses the "option tcp-check" proxy keyword */
5735int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5736 const char *file, int line)
5737{
Christopher Faulet404f9192020-04-09 23:13:54 +02005738 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005739 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5740 int err_code = 0;
5741
5742 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5743 err_code |= ERR_WARN;
5744
5745 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5746 goto out;
5747
Christopher Faulet404f9192020-04-09 23:13:54 +02005748 curpx->options2 &= ~PR_O2_CHK_ANY;
5749 curpx->options2 |= PR_O2_TCPCHK_CHK;
5750
Christopher Fauletd7e63962020-04-17 20:15:59 +02005751 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005752 /* If a tcp-check rulesset is already set, do nothing */
5753 if (rules->list)
5754 goto out;
5755
5756 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5757 * get it.
5758 */
5759 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5760 goto curpx_ruleset;
5761
5762 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5763 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005764 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005765 if (rs)
5766 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005767 }
5768
Christopher Faulet404f9192020-04-09 23:13:54 +02005769 curpx_ruleset:
5770 /* Deduce the ruleset name from the proxy info */
5771 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5772 ((curpx == defpx) ? "defaults" : curpx->id),
5773 curpx->conf.file, curpx->conf.line);
5774
Christopher Faulet61cc8522020-04-20 14:54:42 +02005775 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005776 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005777 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005778 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005779 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5780 goto error;
5781 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005782 }
5783
Christopher Faulet404f9192020-04-09 23:13:54 +02005784 ruleset_found:
5785 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005786 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005787 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005788
5789 out:
5790 return err_code;
5791
5792 error:
5793 err_code |= ERR_ALERT | ERR_FATAL;
5794 goto out;
5795}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005796
5797/* Parses the "option redis-check" proxy keyword */
5798int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5799 const char *file, int line)
5800{
5801 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5802 static char *redis_res = "+PONG\r\n";
5803
5804 struct tcpcheck_ruleset *rs = NULL;
5805 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5806 struct tcpcheck_rule *chk;
5807 char *errmsg = NULL;
5808 int err_code = 0;
5809
5810 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5811 err_code |= ERR_WARN;
5812
5813 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5814 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005815
5816 curpx->options2 &= ~PR_O2_CHK_ANY;
5817 curpx->options2 |= PR_O2_TCPCHK_CHK;
5818
5819 free_tcpcheck_vars(&rules->preset_vars);
5820 rules->list = NULL;
5821 rules->flags = 0;
5822
Christopher Faulet61cc8522020-04-20 14:54:42 +02005823 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005824 if (rs)
5825 goto ruleset_found;
5826
Christopher Faulet61cc8522020-04-20 14:54:42 +02005827 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005828 if (rs == NULL) {
5829 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5830 goto error;
5831 }
5832
5833 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5834 1, curpx, &rs->rules, file, line, &errmsg);
5835 if (!chk) {
5836 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5837 goto error;
5838 }
5839 chk->index = 0;
5840 LIST_ADDQ(&rs->rules, &chk->list);
5841
5842 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5843 "error-status", "L7STS",
5844 "on-error", "%[check.payload(),cut_crlf]",
5845 "on-success", "Redis server is ok",
5846 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005847 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005848 if (!chk) {
5849 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5850 goto error;
5851 }
5852 chk->index = 1;
5853 LIST_ADDQ(&rs->rules, &chk->list);
5854
Christopher Fauletd7cee712020-04-21 13:45:00 +02005855 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005856
5857 ruleset_found:
5858 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005859 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005860
5861 out:
5862 free(errmsg);
5863 return err_code;
5864
5865 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005866 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005867 err_code |= ERR_ALERT | ERR_FATAL;
5868 goto out;
5869}
5870
Christopher Faulet811f78c2020-04-01 11:10:27 +02005871
5872/* Parses the "option ssl-hello-chk" proxy keyword */
5873int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5874 const char *file, int line)
5875{
5876 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5877 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5878 *
5879 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5880 */
5881 static char sslv3_client_hello[] = {
5882 "16" /* ContentType : 0x16 = Hanshake */
5883 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5884 "0079" /* ContentLength : 0x79 bytes after this one */
5885 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5886 "000075" /* HandshakeLength : 0x75 bytes after this one */
5887 "0300" /* Hello Version : 0x0300 = v3 */
5888 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5889 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5890 "00" /* Session ID length : empty (no session ID) */
5891 "004E" /* Cipher Suite Length : 78 bytes after this one */
5892 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5893 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5894 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5895 "000D" "000E" "000F" "0010" /* various bit lengths, */
5896 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5897 "0015" "0016" "0017" "0018"
5898 "0019" "001A" "001B" "002F"
5899 "0030" "0031" "0032" "0033"
5900 "0034" "0035" "0036" "0037"
5901 "0038" "0039" "003A"
5902 "01" /* Compression Length : 0x01 = 1 byte for types */
5903 "00" /* Compression Type : 0x00 = NULL compression */
5904 };
5905
5906 struct tcpcheck_ruleset *rs = NULL;
5907 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5908 struct tcpcheck_rule *chk;
5909 char *errmsg = NULL;
5910 int err_code = 0;
5911
5912 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5913 err_code |= ERR_WARN;
5914
5915 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5916 goto out;
5917
Christopher Faulet811f78c2020-04-01 11:10:27 +02005918 curpx->options2 &= ~PR_O2_CHK_ANY;
5919 curpx->options2 |= PR_O2_TCPCHK_CHK;
5920
5921 free_tcpcheck_vars(&rules->preset_vars);
5922 rules->list = NULL;
5923 rules->flags = 0;
5924
Christopher Faulet61cc8522020-04-20 14:54:42 +02005925 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005926 if (rs)
5927 goto ruleset_found;
5928
Christopher Faulet61cc8522020-04-20 14:54:42 +02005929 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005930 if (rs == NULL) {
5931 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5932 goto error;
5933 }
5934
5935 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5936 1, curpx, &rs->rules, file, line, &errmsg);
5937 if (!chk) {
5938 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5939 goto error;
5940 }
5941 chk->index = 0;
5942 LIST_ADDQ(&rs->rules, &chk->list);
5943
5944 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005945 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005946 "error-status", "L6RSP", "tout-status", "L6TOUT",
5947 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005948 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005949 if (!chk) {
5950 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5951 goto error;
5952 }
5953 chk->index = 1;
5954 LIST_ADDQ(&rs->rules, &chk->list);
5955
Christopher Fauletd7cee712020-04-21 13:45:00 +02005956 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005957
5958 ruleset_found:
5959 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005960 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005961
5962 out:
5963 free(errmsg);
5964 return err_code;
5965
5966 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005967 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005968 err_code |= ERR_ALERT | ERR_FATAL;
5969 goto out;
5970}
5971
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005972/* Parses the "option smtpchk" proxy keyword */
5973int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5974 const char *file, int line)
5975{
5976 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5977
5978 struct tcpcheck_ruleset *rs = NULL;
5979 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5980 struct tcpcheck_rule *chk;
5981 struct tcpcheck_var *var = NULL;
5982 char *cmd = NULL, *errmsg = NULL;
5983 int err_code = 0;
5984
5985 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5986 err_code |= ERR_WARN;
5987
5988 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5989 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005990
5991 curpx->options2 &= ~PR_O2_CHK_ANY;
5992 curpx->options2 |= PR_O2_TCPCHK_CHK;
5993
5994 free_tcpcheck_vars(&rules->preset_vars);
5995 rules->list = NULL;
5996 rules->flags = 0;
5997
5998 cur_arg += 2;
5999 if (*args[cur_arg] && *args[cur_arg+1] &&
6000 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6001 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6002 if (cmd)
6003 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6004 }
6005 else {
6006 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6007 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6008 cmd = strdup("HELO localhost");
6009 }
6010
Christopher Fauletb61caf42020-04-21 10:57:42 +02006011 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006012 if (cmd == NULL || var == NULL) {
6013 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6014 goto error;
6015 }
6016 var->data.type = SMP_T_STR;
6017 var->data.u.str.area = cmd;
6018 var->data.u.str.data = strlen(cmd);
6019 LIST_INIT(&var->list);
6020 LIST_ADDQ(&rules->preset_vars, &var->list);
6021 cmd = NULL;
6022 var = NULL;
6023
Christopher Faulet61cc8522020-04-20 14:54:42 +02006024 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006025 if (rs)
6026 goto ruleset_found;
6027
Christopher Faulet61cc8522020-04-20 14:54:42 +02006028 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006029 if (rs == NULL) {
6030 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6031 goto error;
6032 }
6033
6034 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6035 1, curpx, &rs->rules, file, line, &errmsg);
6036 if (!chk) {
6037 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6038 goto error;
6039 }
6040 chk->index = 0;
6041 LIST_ADDQ(&rs->rules, &chk->list);
6042
6043 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6044 "min-recv", "4",
6045 "error-status", "L7RSP",
6046 "on-error", "%[check.payload(),cut_crlf]",
6047 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006048 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006049 if (!chk) {
6050 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6051 goto error;
6052 }
6053 chk->index = 1;
6054 LIST_ADDQ(&rs->rules, &chk->list);
6055
6056 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6057 "min-recv", "4",
6058 "error-status", "L7STS",
6059 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6060 "status-code", "check.payload(0,3)",
6061 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006062 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006063 if (!chk) {
6064 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6065 goto error;
6066 }
6067 chk->index = 2;
6068 LIST_ADDQ(&rs->rules, &chk->list);
6069
6070 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6071 1, curpx, &rs->rules, file, line, &errmsg);
6072 if (!chk) {
6073 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6074 goto error;
6075 }
6076 chk->index = 3;
6077 LIST_ADDQ(&rs->rules, &chk->list);
6078
6079 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6080 "min-recv", "4",
6081 "error-status", "L7STS",
6082 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6083 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6084 "status-code", "check.payload(0,3)",
6085 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006086 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006087 if (!chk) {
6088 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6089 goto error;
6090 }
6091 chk->index = 4;
6092 LIST_ADDQ(&rs->rules, &chk->list);
6093
Christopher Fauletd7cee712020-04-21 13:45:00 +02006094 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006095
6096 ruleset_found:
6097 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006098 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006099
6100 out:
6101 free(errmsg);
6102 return err_code;
6103
6104 error:
6105 free(cmd);
6106 free(var);
6107 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006108 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006109 err_code |= ERR_ALERT | ERR_FATAL;
6110 goto out;
6111}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006112
Christopher Fauletce355072020-04-02 11:44:39 +02006113/* Parses the "option pgsql-check" proxy keyword */
6114int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6115 const char *file, int line)
6116{
6117 static char pgsql_req[] = {
6118 "%[var(check.plen),htonl,hex]" /* The packet length*/
6119 "00030000" /* the version 3.0 */
6120 "7573657200" /* "user" key */
6121 "%[var(check.username),hex]00" /* the username */
6122 "00"
6123 };
6124
6125 struct tcpcheck_ruleset *rs = NULL;
6126 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6127 struct tcpcheck_rule *chk;
6128 struct tcpcheck_var *var = NULL;
6129 char *user = NULL, *errmsg = NULL;
6130 size_t packetlen = 0;
6131 int err_code = 0;
6132
6133 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6134 err_code |= ERR_WARN;
6135
6136 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6137 goto out;
6138
Christopher Fauletce355072020-04-02 11:44:39 +02006139 curpx->options2 &= ~PR_O2_CHK_ANY;
6140 curpx->options2 |= PR_O2_TCPCHK_CHK;
6141
6142 free_tcpcheck_vars(&rules->preset_vars);
6143 rules->list = NULL;
6144 rules->flags = 0;
6145
6146 cur_arg += 2;
6147 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6148 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6149 file, line, args[0], args[1]);
6150 goto error;
6151 }
6152 if (strcmp(args[cur_arg], "user") == 0) {
6153 packetlen = 15 + strlen(args[cur_arg+1]);
6154 user = strdup(args[cur_arg+1]);
6155
Christopher Fauletb61caf42020-04-21 10:57:42 +02006156 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006157 if (user == NULL || var == NULL) {
6158 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6159 goto error;
6160 }
6161 var->data.type = SMP_T_STR;
6162 var->data.u.str.area = user;
6163 var->data.u.str.data = strlen(user);
6164 LIST_INIT(&var->list);
6165 LIST_ADDQ(&rules->preset_vars, &var->list);
6166 user = NULL;
6167 var = NULL;
6168
Christopher Fauletb61caf42020-04-21 10:57:42 +02006169 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006170 if (var == NULL) {
6171 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6172 goto error;
6173 }
6174 var->data.type = SMP_T_SINT;
6175 var->data.u.sint = packetlen;
6176 LIST_INIT(&var->list);
6177 LIST_ADDQ(&rules->preset_vars, &var->list);
6178 var = NULL;
6179 }
6180 else {
6181 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6182 file, line, args[0], args[1]);
6183 goto error;
6184 }
6185
Christopher Faulet61cc8522020-04-20 14:54:42 +02006186 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006187 if (rs)
6188 goto ruleset_found;
6189
Christopher Faulet61cc8522020-04-20 14:54:42 +02006190 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006191 if (rs == NULL) {
6192 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6193 goto error;
6194 }
6195
6196 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6197 1, curpx, &rs->rules, file, line, &errmsg);
6198 if (!chk) {
6199 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6200 goto error;
6201 }
6202 chk->index = 0;
6203 LIST_ADDQ(&rs->rules, &chk->list);
6204
6205 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6206 1, curpx, &rs->rules, file, line, &errmsg);
6207 if (!chk) {
6208 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6209 goto error;
6210 }
6211 chk->index = 1;
6212 LIST_ADDQ(&rs->rules, &chk->list);
6213
6214 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6215 "min-recv", "5",
6216 "error-status", "L7RSP",
6217 "on-error", "%[check.payload(6,0)]",
6218 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006219 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006220 if (!chk) {
6221 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6222 goto error;
6223 }
6224 chk->index = 2;
6225 LIST_ADDQ(&rs->rules, &chk->list);
6226
Christopher Fauletb841c742020-04-27 18:29:49 +02006227 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
Christopher Fauletce355072020-04-02 11:44:39 +02006228 "min-recv", "9",
6229 "error-status", "L7STS",
6230 "on-success", "PostgreSQL server is ok",
6231 "on-error", "PostgreSQL unknown error",
6232 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006233 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006234 if (!chk) {
6235 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6236 goto error;
6237 }
6238 chk->index = 3;
6239 LIST_ADDQ(&rs->rules, &chk->list);
6240
Christopher Fauletd7cee712020-04-21 13:45:00 +02006241 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006242
6243 ruleset_found:
6244 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006245 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006246
6247 out:
6248 free(errmsg);
6249 return err_code;
6250
6251 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006252 free(user);
6253 free(var);
6254 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006255 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006256 err_code |= ERR_ALERT | ERR_FATAL;
6257 goto out;
6258}
6259
6260
6261/* Parses the "option mysql-check" proxy keyword */
6262int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6263 const char *file, int line)
6264{
6265 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6266 * const char mysql40_client_auth_pkt[] = {
6267 * "\x0e\x00\x00" // packet length
6268 * "\x01" // packet number
6269 * "\x00\x00" // client capabilities
6270 * "\x00\x00\x01" // max packet
6271 * "haproxy\x00" // username (null terminated string)
6272 * "\x00" // filler (always 0x00)
6273 * "\x01\x00\x00" // packet length
6274 * "\x00" // packet number
6275 * "\x01" // COM_QUIT command
6276 * };
6277 */
6278 static char mysql40_rsname[] = "*mysql40-check";
6279 static char mysql40_req[] = {
6280 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6281 "0080" /* client capabilities */
6282 "000001" /* max packet */
6283 "%[var(check.username),hex]00" /* the username */
6284 "00" /* filler (always 0x00) */
6285 "010000" /* packet length*/
6286 "00" /* sequence ID */
6287 "01" /* COM_QUIT command */
6288 };
6289
6290 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6291 * const char mysql41_client_auth_pkt[] = {
6292 * "\x0e\x00\x00\" // packet length
6293 * "\x01" // packet number
6294 * "\x00\x00\x00\x00" // client capabilities
6295 * "\x00\x00\x00\x01" // max packet
6296 * "\x21" // character set (UTF-8)
6297 * char[23] // All zeroes
6298 * "haproxy\x00" // username (null terminated string)
6299 * "\x00" // filler (always 0x00)
6300 * "\x01\x00\x00" // packet length
6301 * "\x00" // packet number
6302 * "\x01" // COM_QUIT command
6303 * };
6304 */
6305 static char mysql41_rsname[] = "*mysql41-check";
6306 static char mysql41_req[] = {
6307 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6308 "00820000" /* client capabilities */
6309 "00800001" /* max packet */
6310 "21" /* character set (UTF-8) */
6311 "000000000000000000000000" /* 23 bytes, al zeroes */
6312 "0000000000000000000000"
6313 "%[var(check.username),hex]00" /* the username */
6314 "00" /* filler (always 0x00) */
6315 "010000" /* packet length*/
6316 "00" /* sequence ID */
6317 "01" /* COM_QUIT command */
6318 };
6319
6320 struct tcpcheck_ruleset *rs = NULL;
6321 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6322 struct tcpcheck_rule *chk;
6323 struct tcpcheck_var *var = NULL;
6324 char *mysql_rsname = "*mysql-check";
6325 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6326 int index = 0, err_code = 0;
6327
6328 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6329 err_code |= ERR_WARN;
6330
6331 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6332 goto out;
6333
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006334 curpx->options2 &= ~PR_O2_CHK_ANY;
6335 curpx->options2 |= PR_O2_TCPCHK_CHK;
6336
6337 free_tcpcheck_vars(&rules->preset_vars);
6338 rules->list = NULL;
6339 rules->flags = 0;
6340
6341 cur_arg += 2;
6342 if (*args[cur_arg]) {
6343 char *user;
6344 int packetlen, userlen;
6345
6346 if (strcmp(args[cur_arg], "user") != 0) {
6347 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6348 file, line, args[0], args[1], args[cur_arg]);
6349 goto error;
6350 }
6351
6352 if (*(args[cur_arg+1]) == 0) {
6353 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6354 file, line, args[0], args[1], args[cur_arg]);
6355 goto error;
6356 }
6357
6358 hdr = calloc(4, sizeof(*hdr));
6359 user = strdup(args[cur_arg+1]);
6360 userlen = strlen(args[cur_arg+1]);
6361
6362 if (hdr == NULL || user == NULL) {
6363 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6364 goto error;
6365 }
6366
6367 if (*args[cur_arg+2]) {
6368 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6369 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6370 file, line, args[cur_arg], args[cur_arg+2]);
6371 goto error;
6372 }
6373 packetlen = userlen + 7 + 27;
6374 mysql_req = mysql41_req;
6375 mysql_rsname = mysql41_rsname;
6376 }
6377 else {
6378 packetlen = userlen + 7;
6379 mysql_req = mysql40_req;
6380 mysql_rsname = mysql40_rsname;
6381 }
6382
6383 hdr[0] = (unsigned char)(packetlen & 0xff);
6384 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6385 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6386 hdr[3] = 1;
6387
Christopher Fauletb61caf42020-04-21 10:57:42 +02006388 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006389 if (var == NULL) {
6390 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6391 goto error;
6392 }
6393 var->data.type = SMP_T_STR;
6394 var->data.u.str.area = hdr;
6395 var->data.u.str.data = 4;
6396 LIST_INIT(&var->list);
6397 LIST_ADDQ(&rules->preset_vars, &var->list);
6398 hdr = NULL;
6399 var = NULL;
6400
Christopher Fauletb61caf42020-04-21 10:57:42 +02006401 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006402 if (var == NULL) {
6403 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6404 goto error;
6405 }
6406 var->data.type = SMP_T_STR;
6407 var->data.u.str.area = user;
6408 var->data.u.str.data = strlen(user);
6409 LIST_INIT(&var->list);
6410 LIST_ADDQ(&rules->preset_vars, &var->list);
6411 user = NULL;
6412 var = NULL;
6413 }
6414
Christopher Faulet61cc8522020-04-20 14:54:42 +02006415 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006416 if (rs)
6417 goto ruleset_found;
6418
Christopher Faulet61cc8522020-04-20 14:54:42 +02006419 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006420 if (rs == NULL) {
6421 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6422 goto error;
6423 }
6424
6425 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6426 1, curpx, &rs->rules, file, line, &errmsg);
6427 if (!chk) {
6428 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6429 goto error;
6430 }
6431 chk->index = index++;
6432 LIST_ADDQ(&rs->rules, &chk->list);
6433
6434 if (mysql_req) {
6435 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6436 1, curpx, &rs->rules, file, line, &errmsg);
6437 if (!chk) {
6438 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6439 goto error;
6440 }
6441 chk->index = index++;
6442 LIST_ADDQ(&rs->rules, &chk->list);
6443 }
6444
6445 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006446 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006447 if (!chk) {
6448 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6449 goto error;
6450 }
6451 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6452 chk->index = index++;
6453 LIST_ADDQ(&rs->rules, &chk->list);
6454
6455 if (mysql_req) {
6456 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006457 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006458 if (!chk) {
6459 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6460 goto error;
6461 }
6462 chk->expect.custom = tcpcheck_mysql_expect_ok;
6463 chk->index = index++;
6464 LIST_ADDQ(&rs->rules, &chk->list);
6465 }
6466
Christopher Fauletd7cee712020-04-21 13:45:00 +02006467 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006468
6469 ruleset_found:
6470 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006471 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006472
6473 out:
6474 free(errmsg);
6475 return err_code;
6476
6477 error:
6478 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006479 free(user);
6480 free(var);
6481 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006482 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006483 err_code |= ERR_ALERT | ERR_FATAL;
6484 goto out;
6485}
6486
Christopher Faulet1997eca2020-04-03 23:13:50 +02006487int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6488 const char *file, int line)
6489{
6490 static char *ldap_req = "300C020101600702010304008000";
6491
6492 struct tcpcheck_ruleset *rs = NULL;
6493 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6494 struct tcpcheck_rule *chk;
6495 char *errmsg = NULL;
6496 int err_code = 0;
6497
6498 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6499 err_code |= ERR_WARN;
6500
6501 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6502 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006503
6504 curpx->options2 &= ~PR_O2_CHK_ANY;
6505 curpx->options2 |= PR_O2_TCPCHK_CHK;
6506
6507 free_tcpcheck_vars(&rules->preset_vars);
6508 rules->list = NULL;
6509 rules->flags = 0;
6510
Christopher Faulet61cc8522020-04-20 14:54:42 +02006511 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006512 if (rs)
6513 goto ruleset_found;
6514
Christopher Faulet61cc8522020-04-20 14:54:42 +02006515 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006516 if (rs == NULL) {
6517 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6518 goto error;
6519 }
6520
6521 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6522 1, curpx, &rs->rules, file, line, &errmsg);
6523 if (!chk) {
6524 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6525 goto error;
6526 }
6527 chk->index = 0;
6528 LIST_ADDQ(&rs->rules, &chk->list);
6529
6530 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6531 "min-recv", "14",
6532 "on-error", "Not LDAPv3 protocol",
6533 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006534 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006535 if (!chk) {
6536 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6537 goto error;
6538 }
6539 chk->index = 1;
6540 LIST_ADDQ(&rs->rules, &chk->list);
6541
6542 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006543 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006544 if (!chk) {
6545 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6546 goto error;
6547 }
6548 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6549 chk->index = 2;
6550 LIST_ADDQ(&rs->rules, &chk->list);
6551
Christopher Fauletd7cee712020-04-21 13:45:00 +02006552 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006553
6554 ruleset_found:
6555 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006556 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006557
6558 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006559 free(errmsg);
6560 return err_code;
6561
6562 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006563 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006564 err_code |= ERR_ALERT | ERR_FATAL;
6565 goto out;
6566}
6567
6568int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6569 const char *file, int line)
6570{
6571 struct tcpcheck_ruleset *rs = NULL;
6572 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6573 struct tcpcheck_rule *chk;
6574 char *spop_req = NULL;
6575 char *errmsg = NULL;
6576 int spop_len = 0, err_code = 0;
6577
6578 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6579 err_code |= ERR_WARN;
6580
6581 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6582 goto out;
6583
Christopher Faulet267b01b2020-04-04 10:27:09 +02006584 curpx->options2 &= ~PR_O2_CHK_ANY;
6585 curpx->options2 |= PR_O2_TCPCHK_CHK;
6586
6587 free_tcpcheck_vars(&rules->preset_vars);
6588 rules->list = NULL;
6589 rules->flags = 0;
6590
6591
Christopher Faulet61cc8522020-04-20 14:54:42 +02006592 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006593 if (rs)
6594 goto ruleset_found;
6595
Christopher Faulet61cc8522020-04-20 14:54:42 +02006596 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006597 if (rs == NULL) {
6598 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6599 goto error;
6600 }
6601
6602 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6603 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6604 goto error;
6605 }
6606 chunk_reset(&trash);
6607 dump_binary(&trash, spop_req, spop_len);
6608 trash.area[trash.data] = '\0';
6609
6610 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6611 1, curpx, &rs->rules, file, line, &errmsg);
6612 if (!chk) {
6613 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6614 goto error;
6615 }
6616 chk->index = 0;
6617 LIST_ADDQ(&rs->rules, &chk->list);
6618
6619 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006620 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006621 if (!chk) {
6622 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6623 goto error;
6624 }
6625 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6626 chk->index = 1;
6627 LIST_ADDQ(&rs->rules, &chk->list);
6628
Christopher Fauletd7cee712020-04-21 13:45:00 +02006629 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006630
6631 ruleset_found:
6632 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006633 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006634
6635 out:
6636 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006637 free(errmsg);
6638 return err_code;
6639
6640 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006641 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006642 err_code |= ERR_ALERT | ERR_FATAL;
6643 goto out;
6644}
Christopher Fauletce355072020-04-02 11:44:39 +02006645
Christopher Faulete5870d82020-04-15 11:32:03 +02006646
6647struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6648{
6649 struct tcpcheck_rule *chk = NULL;
6650 struct tcpcheck_http_hdr *hdr = NULL;
6651 char *meth = NULL, *uri = NULL, *vsn = NULL;
6652 char *hdrs, *body;
6653
6654 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6655 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6656 if (hdrs == body)
6657 hdrs = NULL;
6658 if (hdrs) {
6659 *hdrs = '\0';
6660 hdrs +=2;
6661 }
6662 if (body) {
6663 *body = '\0';
6664 body += 4;
6665 }
6666 if (hdrs || body) {
6667 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6668 " Please, consider to use 'http-check send' directive instead.");
6669 }
6670
6671 chk = calloc(1, sizeof(*chk));
6672 if (!chk) {
6673 memprintf(errmsg, "out of memory");
6674 goto error;
6675 }
6676 chk->action = TCPCHK_ACT_SEND;
6677 chk->send.type = TCPCHK_SEND_HTTP;
6678 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6679 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6680 LIST_INIT(&chk->send.http.hdrs);
6681
6682 /* Copy the method, uri and version */
6683 if (*args[cur_arg]) {
6684 if (!*args[cur_arg+1])
6685 uri = args[cur_arg];
6686 else
6687 meth = args[cur_arg];
6688 }
6689 if (*args[cur_arg+1])
6690 uri = args[cur_arg+1];
6691 if (*args[cur_arg+2])
6692 vsn = args[cur_arg+2];
6693
6694 if (meth) {
6695 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6696 chk->send.http.meth.str.area = strdup(meth);
6697 chk->send.http.meth.str.data = strlen(meth);
6698 if (!chk->send.http.meth.str.area) {
6699 memprintf(errmsg, "out of memory");
6700 goto error;
6701 }
6702 }
6703 if (uri) {
6704 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006705 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006706 memprintf(errmsg, "out of memory");
6707 goto error;
6708 }
6709 }
6710 if (vsn) {
6711 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006712 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006713 memprintf(errmsg, "out of memory");
6714 goto error;
6715 }
6716 }
6717
6718 /* Copy the header */
6719 if (hdrs) {
6720 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6721 struct h1m h1m;
6722 int i, ret;
6723
6724 /* Build and parse the request */
6725 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6726
6727 h1m.flags = H1_MF_HDRS_ONLY;
6728 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6729 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6730 &h1m, NULL);
6731 if (ret <= 0) {
6732 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6733 goto error;
6734 }
6735
Christopher Fauletb61caf42020-04-21 10:57:42 +02006736 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006737 hdr = calloc(1, sizeof(*hdr));
6738 if (!hdr) {
6739 memprintf(errmsg, "out of memory");
6740 goto error;
6741 }
6742 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006743 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006744 if (!hdr->name.ptr) {
6745 memprintf(errmsg, "out of memory");
6746 goto error;
6747 }
6748
Christopher Fauletb61caf42020-04-21 10:57:42 +02006749 ist0(tmp_hdrs[i].v);
6750 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 +02006751 goto error;
6752 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6753 }
6754 }
6755
6756 /* Copy the body */
6757 if (body) {
6758 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006759 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006760 memprintf(errmsg, "out of memory");
6761 goto error;
6762 }
6763 }
6764
6765 return chk;
6766
6767 error:
6768 free_tcpcheck_http_hdr(hdr);
6769 free_tcpcheck(chk, 0);
6770 return NULL;
6771}
6772
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006773int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6774 const char *file, int line)
6775{
Christopher Faulete5870d82020-04-15 11:32:03 +02006776 struct tcpcheck_ruleset *rs = NULL;
6777 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6778 struct tcpcheck_rule *chk;
6779 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006780 int err_code = 0;
6781
6782 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6783 err_code |= ERR_WARN;
6784
6785 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6786 goto out;
6787
Christopher Faulete5870d82020-04-15 11:32:03 +02006788 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6789 if (!chk) {
6790 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6791 goto error;
6792 }
6793 if (errmsg) {
6794 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6795 err_code |= ERR_WARN;
6796 free(errmsg);
6797 errmsg = NULL;
6798 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006799
Christopher Faulete5870d82020-04-15 11:32:03 +02006800 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006801 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006802 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006803
Christopher Faulete5870d82020-04-15 11:32:03 +02006804 free_tcpcheck_vars(&rules->preset_vars);
6805 rules->list = NULL;
6806 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006807
Christopher Faulete5870d82020-04-15 11:32:03 +02006808 /* Deduce the ruleset name from the proxy info */
6809 chunk_printf(&trash, "*http-check-%s_%s-%d",
6810 ((curpx == defpx) ? "defaults" : curpx->id),
6811 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006812
Christopher Faulet61cc8522020-04-20 14:54:42 +02006813 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006814 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006815 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006816 if (rs == NULL) {
6817 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6818 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006819 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006820 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006821
Christopher Faulete5870d82020-04-15 11:32:03 +02006822 rules->list = &rs->rules;
6823 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6824 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6825 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6826 rules->list = NULL;
6827 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006828 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006829
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006830 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006831 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006832 return err_code;
6833
6834 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006835 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006836 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006837 err_code |= ERR_ALERT | ERR_FATAL;
6838 goto out;
6839}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006840
Christopher Faulet6f557912020-04-09 15:58:50 +02006841int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6842 const char *file, int line)
6843{
6844 int err_code = 0;
6845
Christopher Faulet6f557912020-04-09 15:58:50 +02006846 curpx->options2 &= ~PR_O2_CHK_ANY;
6847 curpx->options2 |= PR_O2_EXT_CHK;
6848 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6849 goto out;
6850
6851 out:
6852 return err_code;
6853}
6854
Christopher Fauletce8111e2020-04-06 15:04:11 +02006855/* Parse the "addr" server keyword */
6856static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6857 char **errmsg)
6858{
6859 struct sockaddr_storage *sk;
6860 struct protocol *proto;
6861 int port1, port2, err_code = 0;
6862
6863
6864 if (!*args[*cur_arg+1]) {
6865 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6866 goto error;
6867 }
6868
6869 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6870 if (!sk) {
6871 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6872 goto error;
6873 }
6874
6875 proto = protocol_by_family(sk->ss_family);
6876 if (!proto || !proto->connect) {
6877 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6878 args[*cur_arg], args[*cur_arg+1]);
6879 goto error;
6880 }
6881
6882 if (port1 != port2) {
6883 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6884 args[*cur_arg], args[*cur_arg+1]);
6885 goto error;
6886 }
6887
6888 srv->check.addr = srv->agent.addr = *sk;
6889 srv->flags |= SRV_F_CHECKADDR;
6890 srv->flags |= SRV_F_AGENTADDR;
6891
6892 out:
6893 return err_code;
6894
6895 error:
6896 err_code |= ERR_ALERT | ERR_FATAL;
6897 goto out;
6898}
6899
6900
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006901/* Parse the "agent-addr" server keyword */
6902static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6903 char **errmsg)
6904{
6905 int err_code = 0;
6906
6907 if (!*(args[*cur_arg+1])) {
6908 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6909 goto error;
6910 }
6911 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6912 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6913 goto error;
6914 }
6915
6916 out:
6917 return err_code;
6918
6919 error:
6920 err_code |= ERR_ALERT | ERR_FATAL;
6921 goto out;
6922}
6923
6924/* Parse the "agent-check" server keyword */
6925static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6926 char **errmsg)
6927{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006928 struct tcpcheck_ruleset *rs = NULL;
6929 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6930 struct tcpcheck_rule *chk;
6931 int err_code = 0;
6932
6933 if (srv->do_agent)
6934 goto out;
6935
6936 if (!rules) {
6937 rules = calloc(1, sizeof(*rules));
6938 if (!rules) {
6939 memprintf(errmsg, "out of memory.");
6940 goto error;
6941 }
6942 LIST_INIT(&rules->preset_vars);
6943 srv->agent.tcpcheck_rules = rules;
6944 }
6945 rules->list = NULL;
6946 rules->flags = 0;
6947
Christopher Faulet61cc8522020-04-20 14:54:42 +02006948 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006949 if (rs)
6950 goto ruleset_found;
6951
Christopher Faulet61cc8522020-04-20 14:54:42 +02006952 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006953 if (rs == NULL) {
6954 memprintf(errmsg, "out of memory.");
6955 goto error;
6956 }
6957
6958 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6959 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6960 if (!chk) {
6961 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6962 goto error;
6963 }
6964 chk->index = 0;
6965 LIST_ADDQ(&rs->rules, &chk->list);
6966
6967 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006968 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6969 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006970 if (!chk) {
6971 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6972 goto error;
6973 }
6974 chk->expect.custom = tcpcheck_agent_expect_reply;
6975 chk->index = 1;
6976 LIST_ADDQ(&rs->rules, &chk->list);
6977
Christopher Fauletd7cee712020-04-21 13:45:00 +02006978 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006979
6980 ruleset_found:
6981 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006982 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006983 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006984
6985 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006986 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006987
6988 error:
6989 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006990 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006991 err_code |= ERR_ALERT | ERR_FATAL;
6992 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006993}
6994
6995/* Parse the "agent-inter" server keyword */
6996static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6997 char **errmsg)
6998{
6999 const char *err = NULL;
7000 unsigned int delay;
7001 int err_code = 0;
7002
7003 if (!*(args[*cur_arg+1])) {
7004 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7005 goto error;
7006 }
7007
7008 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7009 if (err == PARSE_TIME_OVER) {
7010 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7011 args[*cur_arg+1], args[*cur_arg], srv->id);
7012 goto error;
7013 }
7014 else if (err == PARSE_TIME_UNDER) {
7015 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7016 args[*cur_arg+1], args[*cur_arg], srv->id);
7017 goto error;
7018 }
7019 else if (err) {
7020 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7021 *err, srv->id);
7022 goto error;
7023 }
7024 if (delay <= 0) {
7025 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7026 delay, args[*cur_arg], srv->id);
7027 goto error;
7028 }
7029 srv->agent.inter = delay;
7030
7031 out:
7032 return err_code;
7033
7034 error:
7035 err_code |= ERR_ALERT | ERR_FATAL;
7036 goto out;
7037}
7038
7039/* Parse the "agent-port" server keyword */
7040static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7041 char **errmsg)
7042{
7043 int err_code = 0;
7044
7045 if (!*(args[*cur_arg+1])) {
7046 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7047 goto error;
7048 }
7049
7050 global.maxsock++;
7051 srv->agent.port = atol(args[*cur_arg+1]);
7052
7053 out:
7054 return err_code;
7055
7056 error:
7057 err_code |= ERR_ALERT | ERR_FATAL;
7058 goto out;
7059}
7060
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007061int set_srv_agent_send(struct server *srv, const char *send)
7062{
7063 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7064 struct tcpcheck_var *var = NULL;
7065 char *str;
7066
7067 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007068 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007069 if (str == NULL || var == NULL)
7070 goto error;
7071
7072 free_tcpcheck_vars(&rules->preset_vars);
7073
7074 var->data.type = SMP_T_STR;
7075 var->data.u.str.area = str;
7076 var->data.u.str.data = strlen(str);
7077 LIST_INIT(&var->list);
7078 LIST_ADDQ(&rules->preset_vars, &var->list);
7079
7080 return 1;
7081
7082 error:
7083 free(str);
7084 free(var);
7085 return 0;
7086}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007087
7088/* Parse the "agent-send" server keyword */
7089static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7090 char **errmsg)
7091{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007092 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007093 int err_code = 0;
7094
7095 if (!*(args[*cur_arg+1])) {
7096 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7097 goto error;
7098 }
7099
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007100 if (!rules) {
7101 rules = calloc(1, sizeof(*rules));
7102 if (!rules) {
7103 memprintf(errmsg, "out of memory.");
7104 goto error;
7105 }
7106 LIST_INIT(&rules->preset_vars);
7107 srv->agent.tcpcheck_rules = rules;
7108 }
7109
7110 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007111 memprintf(errmsg, "out of memory.");
7112 goto error;
7113 }
7114
7115 out:
7116 return err_code;
7117
7118 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007119 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007120 err_code |= ERR_ALERT | ERR_FATAL;
7121 goto out;
7122}
7123
7124/* Parse the "no-agent-send" server keyword */
7125static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7126 char **errmsg)
7127{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007128 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007129 return 0;
7130}
7131
Christopher Fauletce8111e2020-04-06 15:04:11 +02007132/* Parse the "check" server keyword */
7133static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7134 char **errmsg)
7135{
7136 srv->do_check = 1;
7137 return 0;
7138}
7139
7140/* Parse the "check-send-proxy" server keyword */
7141static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7142 char **errmsg)
7143{
7144 srv->check.send_proxy = 1;
7145 return 0;
7146}
7147
7148/* Parse the "check-via-socks4" server keyword */
7149static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7150 char **errmsg)
7151{
7152 srv->check.via_socks4 = 1;
7153 return 0;
7154}
7155
7156/* Parse the "no-check" server keyword */
7157static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7158 char **errmsg)
7159{
7160 deinit_srv_check(srv);
7161 return 0;
7162}
7163
7164/* Parse the "no-check-send-proxy" server keyword */
7165static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7166 char **errmsg)
7167{
7168 srv->check.send_proxy = 0;
7169 return 0;
7170}
7171
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007172/* parse the "check-proto" server keyword */
7173static int srv_parse_check_proto(char **args, int *cur_arg,
7174 struct proxy *px, struct server *newsrv, char **err)
7175{
7176 int err_code = 0;
7177
7178 if (!*args[*cur_arg + 1]) {
7179 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7180 goto error;
7181 }
7182 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7183 if (!newsrv->check.mux_proto) {
7184 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7185 goto error;
7186 }
7187
7188 out:
7189 return err_code;
7190
7191 error:
7192 err_code |= ERR_ALERT | ERR_FATAL;
7193 goto out;
7194}
7195
7196
Christopher Fauletce8111e2020-04-06 15:04:11 +02007197/* Parse the "rise" server keyword */
7198static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7199 char **errmsg)
7200{
7201 int err_code = 0;
7202
7203 if (!*args[*cur_arg + 1]) {
7204 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7205 goto error;
7206 }
7207
7208 srv->check.rise = atol(args[*cur_arg+1]);
7209 if (srv->check.rise <= 0) {
7210 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7211 goto error;
7212 }
7213
7214 if (srv->check.health)
7215 srv->check.health = srv->check.rise;
7216
7217 out:
7218 return err_code;
7219
7220 error:
7221 deinit_srv_agent_check(srv);
7222 err_code |= ERR_ALERT | ERR_FATAL;
7223 goto out;
7224 return 0;
7225}
7226
7227/* Parse the "fall" server keyword */
7228static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7229 char **errmsg)
7230{
7231 int err_code = 0;
7232
7233 if (!*args[*cur_arg + 1]) {
7234 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7235 goto error;
7236 }
7237
7238 srv->check.fall = atol(args[*cur_arg+1]);
7239 if (srv->check.fall <= 0) {
7240 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7241 goto error;
7242 }
7243
7244 out:
7245 return err_code;
7246
7247 error:
7248 deinit_srv_agent_check(srv);
7249 err_code |= ERR_ALERT | ERR_FATAL;
7250 goto out;
7251 return 0;
7252}
7253
7254/* Parse the "inter" server keyword */
7255static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7256 char **errmsg)
7257{
7258 const char *err = NULL;
7259 unsigned int delay;
7260 int err_code = 0;
7261
7262 if (!*(args[*cur_arg+1])) {
7263 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7264 goto error;
7265 }
7266
7267 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7268 if (err == PARSE_TIME_OVER) {
7269 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7270 args[*cur_arg+1], args[*cur_arg], srv->id);
7271 goto error;
7272 }
7273 else if (err == PARSE_TIME_UNDER) {
7274 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7275 args[*cur_arg+1], args[*cur_arg], srv->id);
7276 goto error;
7277 }
7278 else if (err) {
7279 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7280 *err, srv->id);
7281 goto error;
7282 }
7283 if (delay <= 0) {
7284 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7285 delay, args[*cur_arg], srv->id);
7286 goto error;
7287 }
7288 srv->check.inter = delay;
7289
7290 out:
7291 return err_code;
7292
7293 error:
7294 err_code |= ERR_ALERT | ERR_FATAL;
7295 goto out;
7296}
7297
7298
7299/* Parse the "fastinter" server keyword */
7300static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7301 char **errmsg)
7302{
7303 const char *err = NULL;
7304 unsigned int delay;
7305 int err_code = 0;
7306
7307 if (!*(args[*cur_arg+1])) {
7308 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7309 goto error;
7310 }
7311
7312 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7313 if (err == PARSE_TIME_OVER) {
7314 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7315 args[*cur_arg+1], args[*cur_arg], srv->id);
7316 goto error;
7317 }
7318 else if (err == PARSE_TIME_UNDER) {
7319 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7320 args[*cur_arg+1], args[*cur_arg], srv->id);
7321 goto error;
7322 }
7323 else if (err) {
7324 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7325 *err, srv->id);
7326 goto error;
7327 }
7328 if (delay <= 0) {
7329 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7330 delay, args[*cur_arg], srv->id);
7331 goto error;
7332 }
7333 srv->check.fastinter = delay;
7334
7335 out:
7336 return err_code;
7337
7338 error:
7339 err_code |= ERR_ALERT | ERR_FATAL;
7340 goto out;
7341}
7342
7343
7344/* Parse the "downinter" server keyword */
7345static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7346 char **errmsg)
7347{
7348 const char *err = NULL;
7349 unsigned int delay;
7350 int err_code = 0;
7351
7352 if (!*(args[*cur_arg+1])) {
7353 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7354 goto error;
7355 }
7356
7357 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7358 if (err == PARSE_TIME_OVER) {
7359 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7360 args[*cur_arg+1], args[*cur_arg], srv->id);
7361 goto error;
7362 }
7363 else if (err == PARSE_TIME_UNDER) {
7364 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7365 args[*cur_arg+1], args[*cur_arg], srv->id);
7366 goto error;
7367 }
7368 else if (err) {
7369 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7370 *err, srv->id);
7371 goto error;
7372 }
7373 if (delay <= 0) {
7374 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7375 delay, args[*cur_arg], srv->id);
7376 goto error;
7377 }
7378 srv->check.downinter = delay;
7379
7380 out:
7381 return err_code;
7382
7383 error:
7384 err_code |= ERR_ALERT | ERR_FATAL;
7385 goto out;
7386}
7387
7388/* Parse the "port" server keyword */
7389static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7390 char **errmsg)
7391{
7392 int err_code = 0;
7393
7394 if (!*(args[*cur_arg+1])) {
7395 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7396 goto error;
7397 }
7398
7399 global.maxsock++;
7400 srv->check.port = atol(args[*cur_arg+1]);
7401 srv->flags |= SRV_F_CHECKPORT;
7402
7403 out:
7404 return err_code;
7405
7406 error:
7407 err_code |= ERR_ALERT | ERR_FATAL;
7408 goto out;
7409}
7410
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007411static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007412 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7413 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7414 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007415 { 0, NULL, NULL },
7416}};
7417
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007418static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007419 { "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 +02007420 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7421 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7422 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7423 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7424 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007425 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007426 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007427 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7428 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007429 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007430 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7431 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7432 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7433 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7434 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7435 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7436 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7437 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007438 { NULL, NULL, 0 },
7439}};
7440
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007441INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007442INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007443
Willy Tarreaubd741542010-03-16 18:46:54 +01007444/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007445 * Local variables:
7446 * c-indent-level: 8
7447 * c-basic-offset: 8
7448 * End:
7449 */