blob: d1d5cdda024e875913a4bfbf61e89518ff291298 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001205 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001659 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1660 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001686 * addr if specified on the server. otherwise, use the server addr (it
1687 * MUST exist at this step).
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001688 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001689 *conn->dst = (is_addr(&connect->addr)
1690 ? connect->addr
1691 : (is_addr(&check->addr) ? check->addr : s->addr));
1692 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001693
Christopher Faulet61cc8522020-04-20 14:54:42 +02001694 port = 0;
1695 if (!port && connect->port)
1696 port = connect->port;
1697 if (!port && connect->port_expr) {
1698 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001699
Christopher Faulet61cc8522020-04-20 14:54:42 +02001700 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1701 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1702 connect->port_expr, SMP_T_SINT);
1703 if (smp)
1704 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001705 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001706 if (!port && is_inet_addr(&connect->addr))
1707 port = get_host_port(&connect->addr);
1708 if (!port && check->port)
1709 port = check->port;
1710 if (!port && is_inet_addr(&check->addr))
1711 port = get_host_port(&check->addr);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001712 if (!port) {
1713 /* The server MUST exist here */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001714 port = s->svc_port;
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001715 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001716 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001717
Christopher Faulet61cc8522020-04-20 14:54:42 +02001718 xprt = ((connect->options & TCPCHK_OPT_SSL)
1719 ? xprt_get(XPRT_SSL)
1720 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 conn_prepare(conn, proto, xprt);
1723 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001724
Christopher Faulet61cc8522020-04-20 14:54:42 +02001725 status = SF_ERR_INTERNAL;
1726 if (proto && proto->connect) {
1727 struct tcpcheck_rule *next;
1728 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1731 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001732
Christopher Faulet61cc8522020-04-20 14:54:42 +02001733 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1734 if (!next || next->action != TCPCHK_ACT_EXPECT)
1735 flags |= CONNECT_DELACK_ALWAYS;
1736 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001737 }
1738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 if (status != SF_ERR_NONE)
1740 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 conn->flags |= CO_FL_PRIVATE;
1743 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001744
Christopher Faulet61cc8522020-04-20 14:54:42 +02001745 /* The mux may be initialized now if there isn't server attached to the
1746 * check (email alerts) or if there is a mux proto specified or if there
1747 * is no alpn.
1748 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001749 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1750 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001751 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001752
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 if (connect->mux_proto)
1754 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001755 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001756 mux_ops = check->mux_proto->mux;
1757 else {
1758 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1759 ? PROTO_MODE_HTTP
1760 : PROTO_MODE_TCP);
1761
1762 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001763 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001764 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1765 status = SF_ERR_INTERNAL;
1766 goto fail_check;
1767 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001768 }
1769
Christopher Faulet61cc8522020-04-20 14:54:42 +02001770#ifdef USE_OPENSSL
1771 if (connect->sni)
1772 ssl_sock_set_servername(conn, connect->sni);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001773 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001774 ssl_sock_set_servername(conn, s->check.sni);
1775
1776 if (connect->alpn)
1777 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001778 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001779 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1780#endif
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001781 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001782 conn->send_proxy_ofs = 1;
1783 conn->flags |= CO_FL_SOCKS4;
1784 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001785 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001786 conn->send_proxy_ofs = 1;
1787 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001788 }
1789
Christopher Faulet61cc8522020-04-20 14:54:42 +02001790 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1791 conn->send_proxy_ofs = 1;
1792 conn->flags |= CO_FL_SEND_PROXY;
1793 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001794 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001795 conn->send_proxy_ofs = 1;
1796 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001797 }
1798
Christopher Faulet61cc8522020-04-20 14:54:42 +02001799 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1800 /* Some servers don't like reset on close */
1801 fdtab[cs->conn->handle.fd].linger_risk = 0;
1802 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001803
Christopher Faulet61cc8522020-04-20 14:54:42 +02001804 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1805 if (xprt_add_hs(conn) < 0)
1806 status = SF_ERR_RESOURCE;
1807 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001808
Christopher Faulet61cc8522020-04-20 14:54:42 +02001809 fail_check:
1810 /* It can return one of :
1811 * - SF_ERR_NONE if everything's OK
1812 * - SF_ERR_SRVTO if there are no more servers
1813 * - SF_ERR_SRVCL if the connection was refused by the server
1814 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1815 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1816 * - SF_ERR_INTERNAL for any other purely internal errors
1817 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1818 * Note that we try to prevent the network stack from sending the ACK during the
1819 * connect() when a pure TCP check is used (without PROXY protocol).
1820 */
1821 switch (status) {
1822 case SF_ERR_NONE:
1823 /* we allow up to min(inter, timeout.connect) for a connection
1824 * to establish but only when timeout.check is set as it may be
1825 * to short for a full check otherwise
1826 */
1827 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001828
Christopher Faulet61cc8522020-04-20 14:54:42 +02001829 if (proxy->timeout.check && proxy->timeout.connect) {
1830 int t_con = tick_add(now_ms, proxy->timeout.connect);
1831 t->expire = tick_first(t->expire, t_con);
1832 }
1833 break;
1834 case SF_ERR_SRVTO: /* ETIMEDOUT */
1835 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1836 case SF_ERR_PRXCOND:
1837 case SF_ERR_RESOURCE:
1838 case SF_ERR_INTERNAL:
1839 chk_report_conn_err(check, errno, 0);
1840 ret = TCPCHK_EVAL_STOP;
1841 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001842 }
1843
Christopher Faulet61cc8522020-04-20 14:54:42 +02001844 /* don't do anything until the connection is established */
1845 if (conn->flags & CO_FL_WAIT_XPRT) {
1846 ret = TCPCHK_EVAL_WAIT;
1847 goto out;
1848 }
1849
1850 out:
1851 if (conn && check->result == CHK_RES_FAILED)
1852 conn->flags |= CO_FL_ERROR;
1853 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001854}
1855
Christopher Faulet61cc8522020-04-20 14:54:42 +02001856/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1857 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1858 * TCPCHK_EVAL_STOP if an error occurred.
1859 */
1860static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001861{
1862 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001863 struct tcpcheck_send *send = &rule->send;
1864 struct conn_stream *cs = check->cs;
1865 struct connection *conn = cs_conn(cs);
1866 struct buffer *tmp = NULL;
1867 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001868
Christopher Faulet61cc8522020-04-20 14:54:42 +02001869 /* reset the read & write buffer */
1870 b_reset(&check->bi);
1871 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001872
Christopher Faulet61cc8522020-04-20 14:54:42 +02001873 switch (send->type) {
1874 case TCPCHK_SEND_STRING:
1875 case TCPCHK_SEND_BINARY:
1876 if (istlen(send->data) >= b_size(&check->bo)) {
1877 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1878 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1879 tcpcheck_get_step_id(check, rule));
1880 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1881 ret = TCPCHK_EVAL_STOP;
1882 goto out;
1883 }
1884 b_putist(&check->bo, send->data);
1885 break;
1886 case TCPCHK_SEND_STRING_LF:
1887 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1888 if (!b_data(&check->bo))
1889 goto out;
1890 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001891 case TCPCHK_SEND_BINARY_LF: {
1892 int len = 0;
1893
Christopher Faulet61cc8522020-04-20 14:54:42 +02001894 tmp = alloc_trash_chunk();
1895 if (!tmp)
1896 goto error_lf;
1897 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1898 if (!b_data(tmp))
1899 goto out;
1900 tmp->area[tmp->data] = '\0';
1901 b_set_data(&check->bo, b_size(&check->bo));
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001902 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001903 goto error_lf;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001904 check->bo.data = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001905 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001906 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001907 case TCPCHK_SEND_HTTP: {
1908 struct htx_sl *sl;
1909 struct ist meth, uri, vsn, clen, body;
1910 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001911
Christopher Faulet61cc8522020-04-20 14:54:42 +02001912 tmp = alloc_trash_chunk();
1913 if (!tmp)
1914 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001915
Christopher Faulet61cc8522020-04-20 14:54:42 +02001916 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1917 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1918 : http_known_methods[send->http.meth.meth]);
1919 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1920 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001921
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001922 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1923 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001924 slflags |= HTX_SL_F_VER_11;
1925 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1926 if (!isttest(send->http.body))
1927 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001928
Christopher Faulet61cc8522020-04-20 14:54:42 +02001929 htx = htx_from_buf(&check->bo);
1930 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1931 if (!sl)
1932 goto error_htx;
1933 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001934
Christopher Faulet61cc8522020-04-20 14:54:42 +02001935 body = send->http.body; // TODO: handle body_fmt
1936 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001937
Christopher Faulet61cc8522020-04-20 14:54:42 +02001938 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1939 !htx_add_header(htx, ist("Content-length"), clen))
1940 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001941
Christopher Faulet61cc8522020-04-20 14:54:42 +02001942 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1943 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001944
Christopher Faulet61cc8522020-04-20 14:54:42 +02001945 list_for_each_entry(hdr, &send->http.hdrs, list) {
1946 chunk_reset(tmp);
1947 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1948 if (!b_data(tmp))
1949 continue;
1950 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1951 goto error_htx;
1952 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001953
Christopher Faulet61cc8522020-04-20 14:54:42 +02001954 }
1955 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1956 chunk_reset(tmp);
1957 httpchk_build_status_header(check->server, tmp);
1958 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1959 goto error_htx;
1960 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001961
Christopher Faulet61cc8522020-04-20 14:54:42 +02001962 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1963 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1964 !htx_add_endof(htx, HTX_BLK_EOM))
1965 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001966
Christopher Faulet61cc8522020-04-20 14:54:42 +02001967 htx_to_buf(htx, &check->bo);
1968 break;
1969 }
1970 case TCPCHK_SEND_UNDEF:
1971 /* Should never happen. */
1972 ret = TCPCHK_EVAL_STOP;
1973 goto out;
1974 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001975
Christopher Faulet6d471212020-04-22 11:09:25 +02001976
1977 if (conn->mux->snd_buf(cs, &check->bo,
1978 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001979 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001980 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001981 goto out;
1982 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001983 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001984 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001985 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1986 ret = TCPCHK_EVAL_WAIT;
1987 goto out;
1988 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001989
Christopher Faulet61cc8522020-04-20 14:54:42 +02001990 out:
1991 free_trash_chunk(tmp);
1992 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001993
Christopher Faulet61cc8522020-04-20 14:54:42 +02001994 error_htx:
1995 if (htx) {
1996 htx_reset(htx);
1997 htx_to_buf(htx, &check->bo);
1998 }
1999 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
2000 tcpcheck_get_step_id(check, rule));
2001 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2002 ret = TCPCHK_EVAL_STOP;
2003 goto out;
2004
2005 error_lf:
2006 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2007 tcpcheck_get_step_id(check, rule));
2008 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2009 ret = TCPCHK_EVAL_STOP;
2010 goto out;
2011
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002012}
2013
Christopher Faulet61cc8522020-04-20 14:54:42 +02002014/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2015 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2016 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2017 * TCPCHK_EVAL_STOP if an error occurred.
2018 */
2019static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002020{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021 struct conn_stream *cs = check->cs;
2022 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002023 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002024 size_t max, read, cur_read = 0;
2025 int is_empty;
2026 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002027
Christopher Faulet61cc8522020-04-20 14:54:42 +02002028 if (check->wait_list.events & SUB_RETRY_RECV)
2029 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 if (cs->flags & CS_FL_EOS)
2032 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002033
Christopher Faulet61cc8522020-04-20 14:54:42 +02002034 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002035
Christopher Faulet61cc8522020-04-20 14:54:42 +02002036 /* prepare to detect if the mux needs more room */
2037 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002038
Christopher Faulet61cc8522020-04-20 14:54:42 +02002039 while ((cs->flags & CS_FL_RCV_MORE) ||
2040 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2041 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2042 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2043 cur_read += read;
2044 if (!read ||
2045 (cs->flags & CS_FL_WANT_ROOM) ||
2046 (--read_poll <= 0) ||
2047 (read < max && read >= global.tune.recv_enough))
2048 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002049 }
2050
Christopher Faulet61cc8522020-04-20 14:54:42 +02002051 end_recv:
2052 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2053 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2054 /* Report network errors only if we got no other data. Otherwise
2055 * we'll let the upper layers decide whether the response is OK
2056 * or not. It is very common that an RST sent by the server is
2057 * reported as an error just after the last data chunk.
2058 */
2059 goto stop;
2060 }
2061 if (!cur_read) {
2062 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2063 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2064 goto wait_more_data;
2065 }
2066 if (is_empty) {
2067 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2068 tcpcheck_get_step_id(check, rule));
2069 if (rule->comment)
2070 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2071 set_server_check_status(check, rule->expect.err_status, trash.area);
2072 goto stop;
2073 }
2074 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002075
2076 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002077 return ret;
2078
Christopher Faulet61cc8522020-04-20 14:54:42 +02002079 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002080 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002081 goto out;
2082
2083 wait_more_data:
2084 ret = TCPCHK_EVAL_WAIT;
2085 goto out;
2086}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002087
Christopher Faulet61cc8522020-04-20 14:54:42 +02002088/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2089 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2090 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2091 * error occurred.
2092 */
2093static 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 +02002094{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002095 struct htx *htx = htxbuf(&check->bi);
2096 struct htx_sl *sl;
2097 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002098 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002099 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002100 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002101 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002102 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002103 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002104
Christopher Faulet61cc8522020-04-20 14:54:42 +02002105 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002106
Christopher Faulet61cc8522020-04-20 14:54:42 +02002107 if (htx->flags & HTX_FL_PARSING_ERROR) {
2108 status = HCHK_STATUS_L7RSP;
2109 goto error;
2110 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002111
Christopher Faulet61cc8522020-04-20 14:54:42 +02002112 if (htx_is_empty(htx)) {
2113 if (last_read) {
2114 status = HCHK_STATUS_L7RSP;
2115 goto error;
2116 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002117 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002118 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002119
Christopher Faulet61cc8522020-04-20 14:54:42 +02002120 sl = http_get_stline(htx);
2121 check->code = sl->info.res.status;
2122
2123 if (check->server &&
2124 (check->server->proxy->options & PR_O_DISABLE404) &&
2125 (check->server->next_state != SRV_ST_STOPPED) &&
2126 (check->code == 404)) {
2127 /* 404 may be accepted as "stopping" only if the server was up */
2128 goto out;
2129 }
2130
2131 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2132 /* Make GCC happy ; initialize match to a failure state. */
2133 match = inverse;
2134
2135 switch (expect->type) {
2136 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002137 match = 0;
2138 for (i = 0; i < expect->codes.num; i++) {
2139 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2140 sl->info.res.status <= expect->codes.codes[i][1]) {
2141 match = 1;
2142 break;
2143 }
2144 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002145
2146 /* Set status and description in case of error */
2147 status = HCHK_STATUS_L7STS;
2148 desc = htx_sl_res_reason(sl);
2149 break;
2150 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2151 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2152
2153 /* Set status and description in case of error */
2154 status = HCHK_STATUS_L7STS;
2155 desc = htx_sl_res_reason(sl);
2156 break;
2157
2158 case TCPCHK_EXPECT_HTTP_BODY:
2159 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2160 chunk_reset(&trash);
2161 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2162 enum htx_blk_type type = htx_get_blk_type(blk);
2163
2164 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2165 break;
2166 if (type == HTX_BLK_DATA) {
2167 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2168 break;
2169 }
2170 }
2171
2172 if (!b_data(&trash)) {
2173 if (!last_read)
2174 goto wait_more_data;
2175 status = HCHK_STATUS_L7RSP;
2176 desc = ist("HTTP content check could not find a response body");
2177 goto error;
2178 }
2179
2180 if (!last_read &&
2181 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2182 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2183 ret = TCPCHK_EVAL_WAIT;
2184 goto out;
2185 }
2186
2187 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002188 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002189 else
2190 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2191
2192 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002193 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002194 desc = (inverse
2195 ? ist("HTTP check matched unwanted content")
2196 : ist("HTTP content check did not match"));
2197 break;
2198
2199 default:
2200 /* should never happen */
2201 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002202 goto error;
2203 }
2204
Christopher Faulet61cc8522020-04-20 14:54:42 +02002205 /* Wait for more data on mismatch only if no minimum is defined (-1),
2206 * otherwise the absence of match is already conclusive.
2207 */
2208 if (!match && !last_read && (expect->min_recv == -1)) {
2209 ret = TCPCHK_EVAL_WAIT;
2210 goto out;
2211 }
2212
2213 if (!(match ^ inverse))
2214 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002215
2216 out:
2217 free_trash_chunk(msg);
2218 return ret;
2219
2220 error:
2221 ret = TCPCHK_EVAL_STOP;
2222 msg = alloc_trash_chunk();
2223 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002224 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002225 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2226 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002227
2228 wait_more_data:
2229 ret = TCPCHK_EVAL_WAIT;
2230 goto out;
2231}
2232
Christopher Faulet61cc8522020-04-20 14:54:42 +02002233/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2234 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2235 * if an error occurred.
2236 */
2237static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2238{
2239 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2240 struct tcpcheck_expect *expect = &rule->expect;
2241 struct buffer *msg = NULL;
2242 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002243
Christopher Faulet61cc8522020-04-20 14:54:42 +02002244 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002245
Christopher Faulet61cc8522020-04-20 14:54:42 +02002246 /* The current expect might need more data than the previous one, check again
2247 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002248 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002249 if (!last_read) {
2250 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2251 (b_data(&check->bi) < istlen(expect->data))) {
2252 ret = TCPCHK_EVAL_WAIT;
2253 goto out;
2254 }
2255 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2256 ret = TCPCHK_EVAL_WAIT;
2257 goto out;
2258 }
2259 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002260
Christopher Faulet61cc8522020-04-20 14:54:42 +02002261 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2262 /* Make GCC happy ; initialize match to a failure state. */
2263 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002264
Christopher Faulet61cc8522020-04-20 14:54:42 +02002265 switch (expect->type) {
2266 case TCPCHK_EXPECT_STRING:
2267 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002268 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 +02002269 break;
2270 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002271 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 +02002272 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002273
Christopher Faulet61cc8522020-04-20 14:54:42 +02002274 case TCPCHK_EXPECT_REGEX_BINARY:
2275 chunk_reset(&trash);
2276 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002277 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002278 break;
2279 case TCPCHK_EXPECT_CUSTOM:
2280 if (expect->custom)
2281 ret = expect->custom(check, rule, last_read);
2282 goto out;
2283 default:
2284 /* Should never happen. */
2285 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002286 goto out;
2287 }
2288
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002289
Christopher Faulet61cc8522020-04-20 14:54:42 +02002290 /* Wait for more data on mismatch only if no minimum is defined (-1),
2291 * otherwise the absence of match is already conclusive.
2292 */
2293 if (!match && !last_read && (expect->min_recv == -1)) {
2294 ret = TCPCHK_EVAL_WAIT;
2295 goto out;
2296 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002297
Christopher Faulet61cc8522020-04-20 14:54:42 +02002298 /* Result as expected, next rule. */
2299 if (match ^ inverse)
2300 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002301
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002302
Christopher Faulet61cc8522020-04-20 14:54:42 +02002303 /* From this point on, we matched something we did not want, this is an error state. */
2304 ret = TCPCHK_EVAL_STOP;
2305 msg = alloc_trash_chunk();
2306 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002307 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002308 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2309 free_trash_chunk(msg);
2310 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002311
Christopher Faulet61cc8522020-04-20 14:54:42 +02002312 out:
2313 return ret;
2314}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002315
Christopher Faulet61cc8522020-04-20 14:54:42 +02002316/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2317 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2318 * waits.
2319 */
2320static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2321{
2322 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2323 struct act_rule *act_rule;
2324 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002325
Christopher Faulet61cc8522020-04-20 14:54:42 +02002326 act_rule =rule->action_kw.rule;
2327 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2328 if (act_ret != ACT_RET_CONT) {
2329 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2330 tcpcheck_get_step_id(check, rule));
2331 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2332 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002333 }
2334
Christopher Faulet61cc8522020-04-20 14:54:42 +02002335 return ret;
2336}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002337
Christopher Faulet61cc8522020-04-20 14:54:42 +02002338/* Executes a tcp-check ruleset. Note that this is called both from the
2339 * connection's wake() callback and from the check scheduling task. It returns
2340 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2341 * presenting the risk of an fd replacement.
2342 *
2343 * Please do NOT place any return statement in this function and only leave
2344 * via the out_end_tcpcheck label after setting retcode.
2345 */
2346static int tcpcheck_main(struct check *check)
2347{
2348 struct tcpcheck_rule *rule;
2349 struct conn_stream *cs = check->cs;
2350 struct connection *conn = cs_conn(cs);
2351 int must_read = 1, last_read = 0;
2352 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002353
Christopher Faulet61cc8522020-04-20 14:54:42 +02002354 /* here, we know that the check is complete or that it failed */
2355 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002356 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002357
Christopher Faulet61cc8522020-04-20 14:54:42 +02002358 /* 1- check for connection error, if any */
2359 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2360 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002361
Christopher Faulet61cc8522020-04-20 14:54:42 +02002362 /* 2- check if we are waiting for the connection establishment. It only
2363 * happens during TCPCHK_ACT_CONNECT. */
2364 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2365 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2366 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2367 if (rule->action == TCPCHK_ACT_SEND)
2368 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2369 else if (rule->action == TCPCHK_ACT_EXPECT)
2370 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2371 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002372 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002373 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002374
2375 /* 3- check for pending outgoing data. It only happens during
2376 * TCPCHK_ACT_SEND. */
2377 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2378 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002379 ret = conn->mux->snd_buf(cs, &check->bo,
2380 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002381 if (ret <= 0) {
2382 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2383 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002384 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002385 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002386 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2387 goto out;
2388 }
2389 }
2390 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002391 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002392
Christopher Faulet61cc8522020-04-20 14:54:42 +02002393 /* 4- check if a rule must be resume. It happens if check->current_step
2394 * is defined. */
2395 else if (check->current_step)
2396 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002397
Christopher Faulet61cc8522020-04-20 14:54:42 +02002398 /* 5- It is the first evaluation. We must create a session and preset
2399 * tcp-check variables */
2400 else {
2401 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002402
Christopher Faulet61cc8522020-04-20 14:54:42 +02002403 /* First evaluation, create a session */
2404 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2405 if (!check->sess) {
2406 chunk_printf(&trash, "TCPCHK error allocating check session");
2407 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2408 goto out_end_tcpcheck;
2409 }
2410 vars_init(&check->vars, SCOPE_CHECK);
2411 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002412
Christopher Faulet61cc8522020-04-20 14:54:42 +02002413 /* Preset tcp-check variables */
2414 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2415 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002416
Christopher Faulet61cc8522020-04-20 14:54:42 +02002417 memset(&smp, 0, sizeof(smp));
2418 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2419 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002420 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002421 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002422 }
2423
Christopher Faulet61cc8522020-04-20 14:54:42 +02002424 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002425
Christopher Faulet61cc8522020-04-20 14:54:42 +02002426 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2427 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002428
Christopher Faulet61cc8522020-04-20 14:54:42 +02002429 check->code = 0;
2430 switch (rule->action) {
2431 case TCPCHK_ACT_CONNECT:
2432 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002433
Christopher Faulet61cc8522020-04-20 14:54:42 +02002434 /* close but not release yet previous connection */
2435 if (check->cs) {
2436 cs_close(check->cs);
2437 retcode = -1; /* do not reuse the fd in the caller! */
2438 }
2439 eval_ret = tcpcheck_eval_connect(check, rule);
2440 must_read = 1; last_read = 0;
2441 break;
2442 case TCPCHK_ACT_SEND:
2443 check->current_step = rule;
2444 eval_ret = tcpcheck_eval_send(check, rule);
2445 must_read = 1;
2446 break;
2447 case TCPCHK_ACT_EXPECT:
2448 check->current_step = rule;
2449 if (must_read) {
2450 if (check->proxy->timeout.check)
2451 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002452
Christopher Faulet61cc8522020-04-20 14:54:42 +02002453 eval_ret = tcpcheck_eval_recv(check, rule);
2454 if (eval_ret == TCPCHK_EVAL_STOP)
2455 goto out_end_tcpcheck;
2456 else if (eval_ret == TCPCHK_EVAL_WAIT)
2457 goto out;
2458 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2459 must_read = 0;
2460 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002461
Christopher Faulet61cc8522020-04-20 14:54:42 +02002462 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2463 ? tcpcheck_eval_expect_http(check, rule, last_read)
2464 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002465
Christopher Faulet61cc8522020-04-20 14:54:42 +02002466 if (eval_ret == TCPCHK_EVAL_WAIT) {
2467 check->current_step = rule->expect.head;
2468 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2469 }
2470 break;
2471 case TCPCHK_ACT_ACTION_KW:
2472 /* Don't update the current step */
2473 eval_ret = tcpcheck_eval_action_kw(check, rule);
2474 break;
2475 default:
2476 /* Otherwise, just go to the next one and don't update
2477 * the current step
2478 */
2479 eval_ret = TCPCHK_EVAL_CONTINUE;
2480 break;
2481 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002482
Christopher Faulet61cc8522020-04-20 14:54:42 +02002483 switch (eval_ret) {
2484 case TCPCHK_EVAL_CONTINUE:
2485 break;
2486 case TCPCHK_EVAL_WAIT:
2487 goto out;
2488 case TCPCHK_EVAL_STOP:
2489 goto out_end_tcpcheck;
2490 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002491 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002492
Christopher Faulet61cc8522020-04-20 14:54:42 +02002493 /* All rules was evaluated */
2494 if (check->current_step) {
2495 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002496
Christopher Faulet61cc8522020-04-20 14:54:42 +02002497 if (rule->action == TCPCHK_ACT_EXPECT) {
2498 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002499
Christopher Faulet61cc8522020-04-20 14:54:42 +02002500 if (check->server &&
2501 (check->server->proxy->options & PR_O_DISABLE404) &&
2502 (check->server->next_state != SRV_ST_STOPPED) &&
2503 (check->code == 404)) {
2504 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2505 goto out_end_tcpcheck;
2506 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002507
Christopher Faulet61cc8522020-04-20 14:54:42 +02002508 msg = alloc_trash_chunk();
2509 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002510 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002511 set_server_check_status(check, rule->expect.ok_status,
2512 (msg ? b_head(msg) : "(tcp-check)"));
2513 free_trash_chunk(msg);
2514 }
2515 else if (rule->action == TCPCHK_ACT_CONNECT) {
2516 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002517 enum healthcheck_status status = HCHK_STATUS_L4OK;
2518#ifdef USE_OPENSSL
2519 if (conn && ssl_sock_is_ssl(conn))
2520 status = HCHK_STATUS_L6OK;
2521#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002522 set_server_check_status(check, status, msg);
2523 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002524 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002525 else
2526 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002527
Christopher Faulet61cc8522020-04-20 14:54:42 +02002528 out_end_tcpcheck:
2529 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2530 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002531
Christopher Faulet61cc8522020-04-20 14:54:42 +02002532 out:
2533 return retcode;
2534}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002535
Christopher Faulet14cd3162020-04-16 14:50:06 +02002536
Christopher Faulet61cc8522020-04-20 14:54:42 +02002537/**************************************************************************/
2538/************** Health-checks based on an external process ****************/
2539/**************************************************************************/
2540static struct list pid_list = LIST_HEAD_INIT(pid_list);
2541static struct pool_head *pool_head_pid_list;
2542__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002543
Christopher Faulet61cc8522020-04-20 14:54:42 +02002544struct extcheck_env {
2545 char *name; /* environment variable name */
2546 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2547};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002548
Christopher Faulet61cc8522020-04-20 14:54:42 +02002549/* environment variables memory requirement for different types of data */
2550#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2551 * such environment variables are not updatable. */
2552#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2553#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2554#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002555
Christopher Faulet61cc8522020-04-20 14:54:42 +02002556/* external checks environment variables */
2557enum {
2558 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002559
Christopher Faulet61cc8522020-04-20 14:54:42 +02002560 /* Proxy specific environment variables */
2561 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2562 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2563 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2564 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002565
Christopher Faulet61cc8522020-04-20 14:54:42 +02002566 /* Server specific environment variables */
2567 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2568 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2569 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2570 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2571 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2572 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002573
Christopher Faulet61cc8522020-04-20 14:54:42 +02002574 EXTCHK_SIZE
2575};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002576
Christopher Faulet61cc8522020-04-20 14:54:42 +02002577const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2578 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2579 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2580 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2581 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2582 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2583 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2584 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2585 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2586 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2587 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2588 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2589};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002590
Christopher Faulet61cc8522020-04-20 14:54:42 +02002591void block_sigchld(void)
2592{
2593 sigset_t set;
2594 sigemptyset(&set);
2595 sigaddset(&set, SIGCHLD);
2596 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2597}
Willy Tarreaube373152018-09-06 11:45:30 +02002598
Christopher Faulet61cc8522020-04-20 14:54:42 +02002599void unblock_sigchld(void)
2600{
2601 sigset_t set;
2602 sigemptyset(&set);
2603 sigaddset(&set, SIGCHLD);
2604 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002605}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002606
Christopher Faulet61cc8522020-04-20 14:54:42 +02002607static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002608{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002609 struct pid_list *elem;
2610 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002611
Christopher Faulet61cc8522020-04-20 14:54:42 +02002612 elem = pool_alloc(pool_head_pid_list);
2613 if (!elem)
2614 return NULL;
2615 elem->pid = pid;
2616 elem->t = t;
2617 elem->exited = 0;
2618 check->curpid = elem;
2619 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002620
Christopher Faulet61cc8522020-04-20 14:54:42 +02002621 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2622 LIST_ADD(&pid_list, &elem->list);
2623 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625 return elem;
2626}
Christopher Faulete5870d82020-04-15 11:32:03 +02002627
Christopher Faulet61cc8522020-04-20 14:54:42 +02002628static void pid_list_del(struct pid_list *elem)
2629{
2630 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632 if (!elem)
2633 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002634
Christopher Faulet61cc8522020-04-20 14:54:42 +02002635 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2636 LIST_DEL(&elem->list);
2637 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002638
Christopher Faulet61cc8522020-04-20 14:54:42 +02002639 if (!elem->exited)
2640 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002641
Christopher Faulet61cc8522020-04-20 14:54:42 +02002642 check = elem->t->context;
2643 check->curpid = NULL;
2644 pool_free(pool_head_pid_list, elem);
2645}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002646
Christopher Faulet61cc8522020-04-20 14:54:42 +02002647/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2648static void pid_list_expire(pid_t pid, int status)
2649{
2650 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002651
Christopher Faulet61cc8522020-04-20 14:54:42 +02002652 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2653 list_for_each_entry(elem, &pid_list, list) {
2654 if (elem->pid == pid) {
2655 elem->t->expire = now_ms;
2656 elem->status = status;
2657 elem->exited = 1;
2658 task_wakeup(elem->t, TASK_WOKEN_IO);
2659 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002660 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002661 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002662 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2663}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002664
Christopher Faulet61cc8522020-04-20 14:54:42 +02002665static void sigchld_handler(struct sig_handler *sh)
2666{
2667 pid_t pid;
2668 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002669
Christopher Faulet61cc8522020-04-20 14:54:42 +02002670 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2671 pid_list_expire(pid, status);
2672}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002673
Christopher Faulet61cc8522020-04-20 14:54:42 +02002674static int init_pid_list(void)
2675{
2676 if (pool_head_pid_list != NULL)
2677 /* Nothing to do */
2678 return 0;
2679
2680 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2681 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2682 strerror(errno));
2683 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002684 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002685
Christopher Faulet61cc8522020-04-20 14:54:42 +02002686 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2687 if (pool_head_pid_list == NULL) {
2688 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2689 strerror(errno));
2690 return 1;
2691 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002692
Christopher Faulet61cc8522020-04-20 14:54:42 +02002693 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002694}
2695
Christopher Faulet61cc8522020-04-20 14:54:42 +02002696/* helper macro to set an environment variable and jump to a specific label on failure. */
2697#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002698
Christopher Faulet61cc8522020-04-20 14:54:42 +02002699/*
2700 * helper function to allocate enough memory to store an environment variable.
2701 * It will also check that the environment variable is updatable, and silently
2702 * fail if not.
2703 */
2704static int extchk_setenv(struct check *check, int idx, const char *value)
2705{
2706 int len, ret;
2707 char *envname;
2708 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002709
Christopher Faulet61cc8522020-04-20 14:54:42 +02002710 if (idx < 0 || idx >= EXTCHK_SIZE) {
2711 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2712 return 1;
2713 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002714
Christopher Faulet61cc8522020-04-20 14:54:42 +02002715 envname = extcheck_envs[idx].name;
2716 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002717
Christopher Faulet61cc8522020-04-20 14:54:42 +02002718 /* Check if the environment variable is already set, and silently reject
2719 * the update if this one is not updatable. */
2720 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2721 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002722
Christopher Faulet61cc8522020-04-20 14:54:42 +02002723 /* Instead of sending NOT_USED, sending an empty value is preferable */
2724 if (strcmp(value, "NOT_USED") == 0) {
2725 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002726 }
2727
Christopher Faulet61cc8522020-04-20 14:54:42 +02002728 len = strlen(envname) + 1;
2729 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2730 len += strlen(value);
2731 else
2732 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002733
Christopher Faulet61cc8522020-04-20 14:54:42 +02002734 if (!check->envp[idx])
2735 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002736
Christopher Faulet61cc8522020-04-20 14:54:42 +02002737 if (!check->envp[idx]) {
2738 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2739 return 1;
2740 }
2741 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2742 if (ret < 0) {
2743 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2744 return 1;
2745 }
2746 else if (ret > len) {
2747 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2748 return 1;
2749 }
2750 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002751}
2752
Christopher Faulet61cc8522020-04-20 14:54:42 +02002753static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002754{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002755 struct server *s = check->server;
2756 struct proxy *px = s->proxy;
2757 struct listener *listener = NULL, *l;
2758 int i;
2759 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2760 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002761
Christopher Faulet61cc8522020-04-20 14:54:42 +02002762 list_for_each_entry(l, &px->conf.listeners, by_fe)
2763 /* Use the first INET, INET6 or UNIX listener */
2764 if (l->addr.ss_family == AF_INET ||
2765 l->addr.ss_family == AF_INET6 ||
2766 l->addr.ss_family == AF_UNIX) {
2767 listener = l;
2768 break;
2769 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002770
Christopher Faulet61cc8522020-04-20 14:54:42 +02002771 check->curpid = NULL;
2772 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2773 if (!check->envp) {
2774 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2775 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002776 }
2777
Christopher Faulet61cc8522020-04-20 14:54:42 +02002778 check->argv = calloc(6, sizeof(char *));
2779 if (!check->argv) {
2780 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2781 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002782 }
2783
Christopher Faulet61cc8522020-04-20 14:54:42 +02002784 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002785
Christopher Faulet61cc8522020-04-20 14:54:42 +02002786 if (!listener) {
2787 check->argv[1] = strdup("NOT_USED");
2788 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002789 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002790 else if (listener->addr.ss_family == AF_INET ||
2791 listener->addr.ss_family == AF_INET6) {
2792 addr_to_str(&listener->addr, buf, sizeof(buf));
2793 check->argv[1] = strdup(buf);
2794 port_to_str(&listener->addr, buf, sizeof(buf));
2795 check->argv[2] = strdup(buf);
2796 }
2797 else if (listener->addr.ss_family == AF_UNIX) {
2798 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002799
Christopher Faulet61cc8522020-04-20 14:54:42 +02002800 un = (struct sockaddr_un *)&listener->addr;
2801 check->argv[1] = strdup(un->sun_path);
2802 check->argv[2] = strdup("NOT_USED");
2803 }
2804 else {
2805 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2806 goto err;
2807 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002808
Christopher Faulet61cc8522020-04-20 14:54:42 +02002809 if (!check->argv[1] || !check->argv[2]) {
2810 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2811 goto err;
2812 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002813
Christopher Faulet61cc8522020-04-20 14:54:42 +02002814 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2815 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2816 if (!check->argv[3] || !check->argv[4]) {
2817 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2818 goto err;
2819 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002820
Christopher Faulet61cc8522020-04-20 14:54:42 +02002821 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2822 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2823 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002824
Christopher Faulet61cc8522020-04-20 14:54:42 +02002825 for (i = 0; i < 5; i++) {
2826 if (!check->argv[i]) {
2827 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2828 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002829 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002830 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002831
Christopher Faulet61cc8522020-04-20 14:54:42 +02002832 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2833 /* Add proxy environment variables */
2834 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2835 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2836 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2837 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2838 /* Add server environment variables */
2839 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2840 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2841 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2842 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2843 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2844 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002845
Christopher Faulet61cc8522020-04-20 14:54:42 +02002846 /* Ensure that we don't leave any hole in check->envp */
2847 for (i = 0; i < EXTCHK_SIZE; i++)
2848 if (!check->envp[i])
2849 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002850
Christopher Faulet61cc8522020-04-20 14:54:42 +02002851 return 1;
2852err:
2853 if (check->envp) {
2854 for (i = 0; i < EXTCHK_SIZE; i++)
2855 free(check->envp[i]);
2856 free(check->envp);
2857 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002858 }
2859
Christopher Faulet61cc8522020-04-20 14:54:42 +02002860 if (check->argv) {
2861 for (i = 1; i < 5; i++)
2862 free(check->argv[i]);
2863 free(check->argv);
2864 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002865 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002866 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002867}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002868
Christopher Faulet61cc8522020-04-20 14:54:42 +02002869/*
2870 * establish a server health-check that makes use of a process.
2871 *
2872 * It can return one of :
2873 * - SF_ERR_NONE if everything's OK
2874 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2875 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2876 *
2877 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002878 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002879static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002880{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002881 char buf[256];
2882 struct check *check = t->context;
2883 struct server *s = check->server;
2884 struct proxy *px = s->proxy;
2885 int status;
2886 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002887
Christopher Faulet61cc8522020-04-20 14:54:42 +02002888 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002889
Christopher Faulet61cc8522020-04-20 14:54:42 +02002890 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002891
Christopher Faulet61cc8522020-04-20 14:54:42 +02002892 pid = fork();
2893 if (pid < 0) {
2894 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2895 (global.tune.options & GTUNE_INSECURE_FORK) ?
2896 "" : " (likely caused by missing 'insecure-fork-wanted')",
2897 strerror(errno));
2898 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002899 goto out;
2900 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002901 if (pid == 0) {
2902 /* Child */
2903 extern char **environ;
2904 struct rlimit limit;
2905 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002906
Christopher Faulet61cc8522020-04-20 14:54:42 +02002907 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2908 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002909
Christopher Faulet61cc8522020-04-20 14:54:42 +02002910 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002911
Christopher Faulet61cc8522020-04-20 14:54:42 +02002912 /* restore the initial FD limits */
2913 limit.rlim_cur = rlim_fd_cur_at_boot;
2914 limit.rlim_max = rlim_fd_max_at_boot;
2915 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2916 getrlimit(RLIMIT_NOFILE, &limit);
2917 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2918 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2919 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2920 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002921
Christopher Faulet61cc8522020-04-20 14:54:42 +02002922 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002923
Christopher Faulet61cc8522020-04-20 14:54:42 +02002924 /* Update some environment variables and command args: curconn, server addr and server port */
2925 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002926
Christopher Faulet61cc8522020-04-20 14:54:42 +02002927 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2928 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002929
Christopher Faulet61cc8522020-04-20 14:54:42 +02002930 *check->argv[4] = 0;
2931 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2932 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2933 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002934
Christopher Faulet61cc8522020-04-20 14:54:42 +02002935 haproxy_unblock_signals();
2936 execvp(px->check_command, check->argv);
2937 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2938 strerror(errno));
2939 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002940 }
2941
Christopher Faulet61cc8522020-04-20 14:54:42 +02002942 /* Parent */
2943 if (check->result == CHK_RES_UNKNOWN) {
2944 if (pid_list_add(pid, t) != NULL) {
2945 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2946
2947 if (px->timeout.check && px->timeout.connect) {
2948 int t_con = tick_add(now_ms, px->timeout.connect);
2949 t->expire = tick_first(t->expire, t_con);
2950 }
2951 status = SF_ERR_NONE;
2952 goto out;
2953 }
2954 else {
2955 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2956 }
2957 kill(pid, SIGTERM); /* process creation error */
2958 }
2959 else
2960 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2961
2962out:
2963 unblock_sigchld();
2964 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002965}
2966
Christopher Faulet61cc8522020-04-20 14:54:42 +02002967/*
2968 * manages a server health-check that uses an external process. Returns
2969 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002970 *
2971 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002972 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002973 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002974static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002975{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002976 struct check *check = context;
2977 struct server *s = check->server;
2978 int rv;
2979 int ret;
2980 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002981
Christopher Faulet61cc8522020-04-20 14:54:42 +02002982 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2983 if (!(check->state & CHK_ST_INPROGRESS)) {
2984 /* no check currently running */
2985 if (!expired) /* woke up too early */
2986 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002987
Christopher Faulet61cc8522020-04-20 14:54:42 +02002988 /* we don't send any health-checks when the proxy is
2989 * stopped, the server should not be checked or the check
2990 * is disabled.
2991 */
2992 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2993 s->proxy->state == PR_STSTOPPED)
2994 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002995
Christopher Faulet61cc8522020-04-20 14:54:42 +02002996 /* we'll initiate a new check */
2997 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002998
Christopher Faulet61cc8522020-04-20 14:54:42 +02002999 check->state |= CHK_ST_INPROGRESS;
3000
3001 ret = connect_proc_chk(t);
3002 if (ret == SF_ERR_NONE) {
3003 /* the process was forked, we allow up to min(inter,
3004 * timeout.connect) for it to report its status, but
3005 * only when timeout.check is set as it may be to short
3006 * for a full check otherwise.
3007 */
3008 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3009
3010 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3011 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3012 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003013 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003014 task_set_affinity(t, tid_bit);
3015 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003016 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003017
Christopher Faulet61cc8522020-04-20 14:54:42 +02003018 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003019
Christopher Faulet61cc8522020-04-20 14:54:42 +02003020 check->state &= ~CHK_ST_INPROGRESS;
3021 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003022
Christopher Faulet61cc8522020-04-20 14:54:42 +02003023 /* we allow up to min(inter, timeout.connect) for a connection
3024 * to establish but only when timeout.check is set
3025 * as it may be to short for a full check otherwise
3026 */
3027 while (tick_is_expired(t->expire, now_ms)) {
3028 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003029
Christopher Faulet61cc8522020-04-20 14:54:42 +02003030 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3031 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003032
Christopher Faulet61cc8522020-04-20 14:54:42 +02003033 if (s->proxy->timeout.check)
3034 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003035 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003036 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003037 else {
3038 /* there was a test running.
3039 * First, let's check whether there was an uncaught error,
3040 * which can happen on connect timeout or error.
3041 */
3042 if (check->result == CHK_RES_UNKNOWN) {
3043 /* good connection is enough for pure TCP check */
3044 struct pid_list *elem = check->curpid;
3045 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003046
Christopher Faulet61cc8522020-04-20 14:54:42 +02003047 if (elem->exited) {
3048 status = elem->status; /* Save in case the process exits between use below */
3049 if (!WIFEXITED(status))
3050 check->code = -1;
3051 else
3052 check->code = WEXITSTATUS(status);
3053 if (!WIFEXITED(status) || WEXITSTATUS(status))
3054 status = HCHK_STATUS_PROCERR;
3055 else
3056 status = HCHK_STATUS_PROCOK;
3057 } else if (expired) {
3058 status = HCHK_STATUS_PROCTOUT;
3059 ha_warning("kill %d\n", (int)elem->pid);
3060 kill(elem->pid, SIGTERM);
3061 }
3062 set_server_check_status(check, status, NULL);
3063 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003064
Christopher Faulet61cc8522020-04-20 14:54:42 +02003065 if (check->result == CHK_RES_FAILED) {
3066 /* a failure or timeout detected */
3067 check_notify_failure(check);
3068 }
3069 else if (check->result == CHK_RES_CONDPASS) {
3070 /* check is OK but asks for stopping mode */
3071 check_notify_stopping(check);
3072 }
3073 else if (check->result == CHK_RES_PASSED) {
3074 /* a success was detected */
3075 check_notify_success(check);
3076 }
3077 task_set_affinity(t, 1);
3078 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003079
Christopher Faulet61cc8522020-04-20 14:54:42 +02003080 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003081
Christopher Faulet61cc8522020-04-20 14:54:42 +02003082 rv = 0;
3083 if (global.spread_checks > 0) {
3084 rv = srv_getinter(check) * global.spread_checks / 100;
3085 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3086 }
3087 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3088 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003089
Christopher Faulet61cc8522020-04-20 14:54:42 +02003090 reschedule:
3091 while (tick_is_expired(t->expire, now_ms))
3092 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003093
Christopher Faulet61cc8522020-04-20 14:54:42 +02003094 out_unlock:
3095 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3096 return t;
3097}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003098
Baptiste Assmann248f1172018-03-01 21:49:01 +01003099
Christopher Faulet61cc8522020-04-20 14:54:42 +02003100/**************************************************************************/
3101/***************** Health-checks based on connections *********************/
3102/**************************************************************************/
3103/* This function is used only for server health-checks. It handles connection
3104 * status updates including errors. If necessary, it wakes the check task up.
3105 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3106 * connection (eg: reconnect). It relies on tcpcheck_main().
3107 */
3108static int wake_srv_chk(struct conn_stream *cs)
3109{
3110 struct connection *conn = cs->conn;
3111 struct check *check = cs->data;
3112 struct email_alertq *q = container_of(check, typeof(*q), check);
3113 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003114
Christopher Faulet61cc8522020-04-20 14:54:42 +02003115 if (check->server)
3116 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3117 else
3118 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003119
Christopher Faulet61cc8522020-04-20 14:54:42 +02003120 /* we may have to make progress on the TCP checks */
3121 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003122
Christopher Faulet61cc8522020-04-20 14:54:42 +02003123 cs = check->cs;
3124 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003125
Christopher Faulet61cc8522020-04-20 14:54:42 +02003126 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3127 /* We may get error reports bypassing the I/O handlers, typically
3128 * the case when sending a pure TCP check which fails, then the I/O
3129 * handlers above are not called. This is completely handled by the
3130 * main processing task so let's simply wake it up. If we get here,
3131 * we expect errno to still be valid.
3132 */
3133 chk_report_conn_err(check, errno, 0);
3134 task_wakeup(check->task, TASK_WOKEN_IO);
3135 }
3136
3137 if (check->result != CHK_RES_UNKNOWN) {
3138 /* Check complete or aborted. If connection not yet closed do it
3139 * now and wake the check task up to be sure the result is
3140 * handled ASAP. */
3141 conn_sock_drain(conn);
3142 cs_close(cs);
3143 ret = -1;
3144 /* We may have been scheduled to run, and the
3145 * I/O handler expects to have a cs, so remove
3146 * the tasklet
3147 */
3148 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3149 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003150 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003151
3152 if (check->server)
3153 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003154 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003155 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003156
Christopher Faulet61cc8522020-04-20 14:54:42 +02003157 /* if a connection got replaced, we must absolutely prevent the connection
3158 * handler from touching its fd, and perform the FD polling updates ourselves
3159 */
3160 if (ret < 0)
3161 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003162
Christopher Faulet61cc8522020-04-20 14:54:42 +02003163 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003164}
3165
Christopher Faulet61cc8522020-04-20 14:54:42 +02003166/* This function checks if any I/O is wanted, and if so, attempts to do so */
3167static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003168{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003169 struct check *check = ctx;
3170 struct conn_stream *cs = check->cs;
3171 struct email_alertq *q = container_of(check, typeof(*q), check);
3172 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003173
Christopher Faulet61cc8522020-04-20 14:54:42 +02003174 if (!(check->wait_list.events & SUB_RETRY_SEND))
3175 ret = wake_srv_chk(cs);
3176 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3177 if (check->server)
3178 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3179 else
3180 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003181
Christopher Faulet61cc8522020-04-20 14:54:42 +02003182 if (unlikely(check->result == CHK_RES_FAILED)) {
3183 /* collect possible new errors */
3184 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3185 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003186
Christopher Faulet61cc8522020-04-20 14:54:42 +02003187 /* Reset the check buffer... */
3188 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003189
Christopher Faulet61cc8522020-04-20 14:54:42 +02003190 /* Close the connection... We still attempt to nicely close if,
3191 * for instance, SSL needs to send a "close notify." Later, we perform
3192 * a hard close and reset the connection if some data are pending,
3193 * otherwise we end up with many TIME_WAITs and eat all the source port
3194 * range quickly. To avoid sending RSTs all the time, we first try to
3195 * drain pending data.
3196 */
3197 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3198 * connection, to make sure cs_shutw() will not lead to a shutdown()
3199 * that would provoke TIME_WAITs.
3200 */
3201 cs_shutr(cs, CS_SHR_DRAIN);
3202 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003203
Christopher Faulet61cc8522020-04-20 14:54:42 +02003204 /* OK, let's not stay here forever */
3205 if (check->result == CHK_RES_FAILED)
3206 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003207
Christopher Faulet61cc8522020-04-20 14:54:42 +02003208 task_wakeup(t, TASK_WOKEN_IO);
3209 }
3210
3211 if (check->server)
3212 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3213 else
3214 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003215 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003216 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003217}
3218
Christopher Faulet61cc8522020-04-20 14:54:42 +02003219/* manages a server health-check that uses a connection. Returns
3220 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3221 *
3222 * Please do NOT place any return statement in this function and only leave
3223 * via the out_unlock label.
3224 */
3225static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003226{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003227 struct check *check = context;
3228 struct proxy *proxy = check->proxy;
3229 struct conn_stream *cs = check->cs;
3230 struct connection *conn = cs_conn(cs);
3231 int rv;
3232 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003233
Christopher Faulet61cc8522020-04-20 14:54:42 +02003234 if (check->server)
3235 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3236 if (!(check->state & CHK_ST_INPROGRESS)) {
3237 /* no check currently running */
3238 if (!expired) /* woke up too early */
3239 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003240
Christopher Faulet61cc8522020-04-20 14:54:42 +02003241 /* we don't send any health-checks when the proxy is
3242 * stopped, the server should not be checked or the check
3243 * is disabled.
3244 */
3245 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3246 proxy->state == PR_STSTOPPED)
3247 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003248
Christopher Faulet61cc8522020-04-20 14:54:42 +02003249 /* we'll initiate a new check */
3250 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003251
Christopher Faulet61cc8522020-04-20 14:54:42 +02003252 check->state |= CHK_ST_INPROGRESS;
3253 b_reset(&check->bi);
3254 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003255
Christopher Faulet61cc8522020-04-20 14:54:42 +02003256 task_set_affinity(t, tid_bit);
3257 cs = check->cs;
3258 conn = cs_conn(cs);
3259 if (!conn) {
3260 check->current_step = NULL;
3261 tcpcheck_main(check);
3262 goto out_unlock;
3263 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003264
Christopher Faulet61cc8522020-04-20 14:54:42 +02003265 conn->flags |= CO_FL_ERROR;
3266 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003267
Christopher Faulet61cc8522020-04-20 14:54:42 +02003268 /* here, we have seen a synchronous error, no fd was allocated */
3269 task_set_affinity(t, MAX_THREADS_MASK);
3270 if (cs) {
3271 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003272 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003273 /* We may have been scheduled to run, and the
3274 * I/O handler expects to have a cs, so remove
3275 * the tasklet
3276 */
3277 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3278 cs_destroy(cs);
3279 cs = check->cs = NULL;
3280 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003281 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003282
Christopher Faulet61cc8522020-04-20 14:54:42 +02003283 check->state &= ~CHK_ST_INPROGRESS;
3284 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003285
Christopher Faulet61cc8522020-04-20 14:54:42 +02003286 /* we allow up to min(inter, timeout.connect) for a connection
3287 * to establish but only when timeout.check is set
3288 * as it may be to short for a full check otherwise
3289 */
3290 while (tick_is_expired(t->expire, now_ms)) {
3291 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003292
Christopher Faulet61cc8522020-04-20 14:54:42 +02003293 t_con = tick_add(t->expire, proxy->timeout.connect);
3294 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3295 if (proxy->timeout.check)
3296 t->expire = tick_first(t->expire, t_con);
3297 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003298 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003299 else {
3300 /* there was a test running.
3301 * First, let's check whether there was an uncaught error,
3302 * which can happen on connect timeout or error.
3303 */
3304 if (check->result == CHK_RES_UNKNOWN) {
3305 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3306 chk_report_conn_err(check, 0, expired);
3307 }
3308 else
3309 goto out_unlock; /* timeout not reached, wait again */
3310 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003311
Christopher Faulet61cc8522020-04-20 14:54:42 +02003312 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003313
Christopher Faulet61cc8522020-04-20 14:54:42 +02003314 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003315
Christopher Faulet61cc8522020-04-20 14:54:42 +02003316 if (conn && conn->xprt) {
3317 /* The check was aborted and the connection was not yet closed.
3318 * This can happen upon timeout, or when an external event such
3319 * as a failed response coupled with "observe layer7" caused the
3320 * server state to be suddenly changed.
3321 */
3322 conn_sock_drain(conn);
3323 cs_close(cs);
3324 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003325
Christopher Faulet61cc8522020-04-20 14:54:42 +02003326 if (cs) {
3327 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003328 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003329 /* We may have been scheduled to run, and the
3330 * I/O handler expects to have a cs, so remove
3331 * the tasklet
3332 */
3333 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3334 cs_destroy(cs);
3335 cs = check->cs = NULL;
3336 conn = NULL;
3337 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003338
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003339 if (check->sess != NULL) {
3340 vars_prune(&check->vars, check->sess, NULL);
3341 session_free(check->sess);
3342 check->sess = NULL;
3343 }
3344
Christopher Faulet61cc8522020-04-20 14:54:42 +02003345 if (check->server) {
3346 if (check->result == CHK_RES_FAILED) {
3347 /* a failure or timeout detected */
3348 check_notify_failure(check);
3349 }
3350 else if (check->result == CHK_RES_CONDPASS) {
3351 /* check is OK but asks for stopping mode */
3352 check_notify_stopping(check);
3353 }
3354 else if (check->result == CHK_RES_PASSED) {
3355 /* a success was detected */
3356 check_notify_success(check);
3357 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003358 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003359 task_set_affinity(t, MAX_THREADS_MASK);
3360 check->state &= ~CHK_ST_INPROGRESS;
3361
3362 if (check->server) {
3363 rv = 0;
3364 if (global.spread_checks > 0) {
3365 rv = srv_getinter(check) * global.spread_checks / 100;
3366 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3367 }
3368 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003369 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003370 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003371
Christopher Faulet61cc8522020-04-20 14:54:42 +02003372 reschedule:
3373 while (tick_is_expired(t->expire, now_ms))
3374 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3375 out_unlock:
3376 if (check->server)
3377 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3378 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003379}
3380
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003381
Christopher Faulet61cc8522020-04-20 14:54:42 +02003382/**************************************************************************/
3383/******************* Internals to parse tcp-check rules *******************/
3384/**************************************************************************/
3385struct action_kw_list tcp_check_keywords = {
3386 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3387};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003388
Christopher Faulet61cc8522020-04-20 14:54:42 +02003389/* Return the struct action_kw associated to a keyword */
3390static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003391{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003392 return action_lookup(&tcp_check_keywords.list, kw);
3393}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003394
Christopher Faulet61cc8522020-04-20 14:54:42 +02003395static void action_kw_tcp_check_build_list(struct buffer *chk)
3396{
3397 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003398}
3399
Christopher Faulet61cc8522020-04-20 14:54:42 +02003400/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3401 * returned on error.
3402 */
3403static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3404 struct list *rules, struct action_kw *kw,
3405 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003406{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003407 struct tcpcheck_rule *chk = NULL;
3408 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003409
Christopher Faulet61cc8522020-04-20 14:54:42 +02003410 actrule = calloc(1, sizeof(*actrule));
3411 if (!actrule) {
3412 memprintf(errmsg, "out of memory");
3413 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003414 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003415 actrule->kw = kw;
3416 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003417
Christopher Faulet61cc8522020-04-20 14:54:42 +02003418 cur_arg++;
3419 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3420 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3421 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003422 }
3423
Christopher Faulet61cc8522020-04-20 14:54:42 +02003424 chk = calloc(1, sizeof(*chk));
3425 if (!chk) {
3426 memprintf(errmsg, "out of memory");
3427 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003428 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003429 chk->action = TCPCHK_ACT_ACTION_KW;
3430 chk->action_kw.rule = actrule;
3431 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003432
3433 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003434 free(actrule);
3435 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003436}
3437
Christopher Faulet61cc8522020-04-20 14:54:42 +02003438/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3439 * returned on error.
3440 */
3441static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3442 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003443{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003444 struct tcpcheck_rule *chk = NULL;
3445 struct sockaddr_storage *sk = NULL;
3446 char *comment = NULL, *sni = NULL, *alpn = NULL;
3447 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003448 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003449 unsigned short conn_opts = 0;
3450 long port = 0;
3451 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003452
Christopher Faulet61cc8522020-04-20 14:54:42 +02003453 list_for_each_entry(chk, rules, list) {
3454 if (chk->action == TCPCHK_ACT_CONNECT)
3455 break;
3456 if (chk->action == TCPCHK_ACT_COMMENT ||
3457 chk->action == TCPCHK_ACT_ACTION_KW ||
3458 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3459 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003460
Christopher Faulet61cc8522020-04-20 14:54:42 +02003461 memprintf(errmsg, "first step MUST also be a 'connect', "
3462 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3463 "when there is a 'connect' step in the tcp-check ruleset");
3464 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003465 }
3466
Christopher Faulet61cc8522020-04-20 14:54:42 +02003467 cur_arg++;
3468 while (*(args[cur_arg])) {
3469 if (strcmp(args[cur_arg], "default") == 0)
3470 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3471 else if (strcmp(args[cur_arg], "addr") == 0) {
3472 int port1, port2;
3473 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003474
Christopher Faulet61cc8522020-04-20 14:54:42 +02003475 if (!*(args[cur_arg+1])) {
3476 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3477 goto error;
3478 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003479
Christopher Faulet61cc8522020-04-20 14:54:42 +02003480 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3481 if (!sk) {
3482 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3483 goto error;
3484 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003485
Christopher Faulet61cc8522020-04-20 14:54:42 +02003486 proto = protocol_by_family(sk->ss_family);
3487 if (!proto || !proto->connect) {
3488 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3489 args[cur_arg]);
3490 goto error;
3491 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003492
Christopher Faulet61cc8522020-04-20 14:54:42 +02003493 if (port1 != port2) {
3494 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3495 args[cur_arg], args[cur_arg+1]);
3496 goto error;
3497 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003498
Christopher Faulet61cc8522020-04-20 14:54:42 +02003499 cur_arg++;
3500 }
3501 else if (strcmp(args[cur_arg], "port") == 0) {
3502 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003503
Christopher Faulet61cc8522020-04-20 14:54:42 +02003504 if (!*(args[cur_arg+1])) {
3505 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3506 goto error;
3507 }
3508 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003509
Christopher Faulet61cc8522020-04-20 14:54:42 +02003510 port = 0;
3511 release_sample_expr(port_expr);
3512 p = args[cur_arg]; end = p + strlen(p);
3513 port = read_uint(&p, end);
3514 if (p != end) {
3515 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003516
Christopher Faulet61cc8522020-04-20 14:54:42 +02003517 px->conf.args.ctx = ARGC_SRV;
3518 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3519 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003520
Christopher Faulet61cc8522020-04-20 14:54:42 +02003521 if (!port_expr) {
3522 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3523 goto error;
3524 }
3525 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3526 memprintf(errmsg, "error detected while parsing port expression : "
3527 " fetch method '%s' extracts information from '%s', "
3528 "none of which is available here.\n",
3529 args[cur_arg], sample_src_names(port_expr->fetch->use));
3530 goto error;
3531 }
3532 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3533 }
3534 else if (port > 65535 || port < 1) {
3535 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3536 args[cur_arg]);
3537 goto error;
3538 }
3539 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003540 else if (strcmp(args[cur_arg], "proto") == 0) {
3541 if (!*(args[cur_arg+1])) {
3542 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3543 goto error;
3544 }
3545 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3546 if (!mux_proto) {
3547 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3548 goto error;
3549 }
3550 cur_arg++;
3551 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003552 else if (strcmp(args[cur_arg], "comment") == 0) {
3553 if (!*(args[cur_arg+1])) {
3554 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3555 goto error;
3556 }
3557 cur_arg++;
3558 free(comment);
3559 comment = strdup(args[cur_arg]);
3560 if (!comment) {
3561 memprintf(errmsg, "out of memory");
3562 goto error;
3563 }
3564 }
3565 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3566 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3567 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3568 conn_opts |= TCPCHK_OPT_SOCKS4;
3569 else if (strcmp(args[cur_arg], "linger") == 0)
3570 conn_opts |= TCPCHK_OPT_LINGER;
3571#ifdef USE_OPENSSL
3572 else if (strcmp(args[cur_arg], "ssl") == 0) {
3573 px->options |= PR_O_TCPCHK_SSL;
3574 conn_opts |= TCPCHK_OPT_SSL;
3575 }
3576 else if (strcmp(args[cur_arg], "sni") == 0) {
3577 if (!*(args[cur_arg+1])) {
3578 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3579 goto error;
3580 }
3581 cur_arg++;
3582 free(sni);
3583 sni = strdup(args[cur_arg]);
3584 if (!sni) {
3585 memprintf(errmsg, "out of memory");
3586 goto error;
3587 }
3588 }
3589 else if (strcmp(args[cur_arg], "alpn") == 0) {
3590#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3591 free(alpn);
3592 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3593 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3594 goto error;
3595 }
3596 cur_arg++;
3597#else
3598 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003599 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003600#endif
3601 }
3602#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003603
Christopher Faulet61cc8522020-04-20 14:54:42 +02003604 else {
3605 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3606#ifdef USE_OPENSSL
3607 ", 'ssl', 'sni', 'alpn'"
3608#endif /* USE_OPENSSL */
3609 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3610 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003611 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003612 }
3613 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003614 }
3615
Christopher Faulet61cc8522020-04-20 14:54:42 +02003616 chk = calloc(1, sizeof(*chk));
3617 if (!chk) {
3618 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003619 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003620 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003621 chk->action = TCPCHK_ACT_CONNECT;
3622 chk->comment = comment;
3623 chk->connect.port = port;
3624 chk->connect.options = conn_opts;
3625 chk->connect.sni = sni;
3626 chk->connect.alpn = alpn;
3627 chk->connect.alpn_len= alpn_len;
3628 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003629 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003630 if (sk)
3631 chk->connect.addr = *sk;
3632 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003633
Christopher Faulet61cc8522020-04-20 14:54:42 +02003634 error:
3635 free(alpn);
3636 free(sni);
3637 free(comment);
3638 release_sample_expr(port_expr);
3639 return NULL;
3640}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003641
Christopher Faulet61cc8522020-04-20 14:54:42 +02003642/* Parses and creates a tcp-check send rule. NULL is returned on error */
3643static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3644 const char *file, int line, char **errmsg)
3645{
3646 struct tcpcheck_rule *chk = NULL;
3647 char *comment = NULL, *data = NULL;
3648 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003649
Christopher Faulet61cc8522020-04-20 14:54:42 +02003650 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3651 if (!*(args[cur_arg+1])) {
3652 memprintf(errmsg, "'%s' expects a %s as argument",
3653 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003654 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003655 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656
Christopher Faulet61cc8522020-04-20 14:54:42 +02003657 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003658
Christopher Faulet61cc8522020-04-20 14:54:42 +02003659 cur_arg += 2;
3660 while (*(args[cur_arg])) {
3661 if (strcmp(args[cur_arg], "comment") == 0) {
3662 if (!*(args[cur_arg+1])) {
3663 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3664 goto error;
3665 }
3666 cur_arg++;
3667 free(comment);
3668 comment = strdup(args[cur_arg]);
3669 if (!comment) {
3670 memprintf(errmsg, "out of memory");
3671 goto error;
3672 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003673 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003674 else if (strcmp(args[cur_arg], "log-format") == 0) {
3675 if (type == TCPCHK_SEND_BINARY)
3676 type = TCPCHK_SEND_BINARY_LF;
3677 else if (type == TCPCHK_SEND_STRING)
3678 type = TCPCHK_SEND_STRING_LF;
3679 }
3680 else {
3681 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3682 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003683 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003684 }
3685 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003686 }
3687
Christopher Faulet61cc8522020-04-20 14:54:42 +02003688 chk = calloc(1, sizeof(*chk));
3689 if (!chk) {
3690 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003691 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003692 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003693 chk->action = TCPCHK_ACT_SEND;
3694 chk->comment = comment;
3695 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003696
Christopher Faulet61cc8522020-04-20 14:54:42 +02003697 switch (chk->send.type) {
3698 case TCPCHK_SEND_STRING:
3699 chk->send.data = ist2(strdup(data), strlen(data));
3700 if (!isttest(chk->send.data)) {
3701 memprintf(errmsg, "out of memory");
3702 goto error;
3703 }
3704 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003705 case TCPCHK_SEND_BINARY: {
3706 int len = 0;
3707 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003708 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3709 goto error;
3710 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003711 chk->send.data.len = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003712 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003713 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003714 case TCPCHK_SEND_STRING_LF:
3715 case TCPCHK_SEND_BINARY_LF:
3716 LIST_INIT(&chk->send.fmt);
3717 px->conf.args.ctx = ARGC_SRV;
3718 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3719 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3720 goto error;
3721 }
3722 break;
3723 case TCPCHK_SEND_HTTP:
3724 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003725 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003726 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003727
Christopher Faulet61cc8522020-04-20 14:54:42 +02003728 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729
Christopher Faulet61cc8522020-04-20 14:54:42 +02003730 error:
3731 free(chk);
3732 free(comment);
3733 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003734}
3735
Christopher Faulet61cc8522020-04-20 14:54:42 +02003736/* Parses and creates a http-check send rule. NULL is returned on error */
3737static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3738 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003739{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003740 struct tcpcheck_rule *chk = NULL;
3741 struct tcpcheck_http_hdr *hdr = NULL;
3742 struct http_hdr hdrs[global.tune.max_http_hdr];
3743 char *meth = NULL, *uri = NULL, *vsn = NULL;
3744 char *body = NULL, *comment = NULL;
3745 unsigned int flags = 0;
3746 int i = 0;
3747
3748 cur_arg++;
3749 while (*(args[cur_arg])) {
3750 if (strcmp(args[cur_arg], "meth") == 0) {
3751 if (!*(args[cur_arg+1])) {
3752 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3753 goto error;
3754 }
3755 cur_arg++;
3756 meth = args[cur_arg];
3757 }
3758 else if (strcmp(args[cur_arg], "uri") == 0) {
3759 if (!*(args[cur_arg+1])) {
3760 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3761 goto error;
3762 }
3763 cur_arg++;
3764 uri = args[cur_arg];
3765 // TODO: log-format uri
3766 }
Christopher Faulet907701b2020-04-28 09:37:00 +02003767 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003768 if (!*(args[cur_arg+1])) {
3769 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3770 goto error;
3771 }
3772 cur_arg++;
3773 vsn = args[cur_arg];
3774 }
3775 else if (strcmp(args[cur_arg], "hdr") == 0) {
3776 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3777 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3778 goto error;
3779 }
3780 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3781 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3782 i++;
3783 cur_arg += 2;
3784 }
3785 else if (strcmp(args[cur_arg], "body") == 0) {
3786 if (!*(args[cur_arg+1])) {
3787 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3788 goto error;
3789 }
3790 cur_arg++;
3791 body = args[cur_arg];
3792 // TODO: log-format body
3793 }
3794 else if (strcmp(args[cur_arg], "comment") == 0) {
3795 if (!*(args[cur_arg+1])) {
3796 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3797 goto error;
3798 }
3799 cur_arg++;
3800 free(comment);
3801 comment = strdup(args[cur_arg]);
3802 if (!comment) {
3803 memprintf(errmsg, "out of memory");
3804 goto error;
3805 }
3806 }
3807 else {
Christopher Faulet907701b2020-04-28 09:37:00 +02003808 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'ver', 'hdr' and 'body' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003809 args[cur_arg]);
3810 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003811 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003812 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003813 }
3814
Christopher Faulet61cc8522020-04-20 14:54:42 +02003815 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003816
Christopher Faulet61cc8522020-04-20 14:54:42 +02003817 chk = calloc(1, sizeof(*chk));
3818 if (!chk) {
3819 memprintf(errmsg, "out of memory");
3820 goto error;
3821 }
3822 chk->action = TCPCHK_ACT_SEND;
3823 chk->comment = comment; comment = NULL;
3824 chk->send.type = TCPCHK_SEND_HTTP;
3825 chk->send.http.flags = flags;
3826 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003827
Christopher Faulet61cc8522020-04-20 14:54:42 +02003828 if (meth) {
3829 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3830 chk->send.http.meth.str.area = strdup(meth);
3831 chk->send.http.meth.str.data = strlen(meth);
3832 if (!chk->send.http.meth.str.area) {
3833 memprintf(errmsg, "out of memory");
3834 goto error;
3835 }
3836 }
3837 if (uri) {
3838 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3839 if (!isttest(chk->send.http.uri)) {
3840 memprintf(errmsg, "out of memory");
3841 goto error;
3842 }
3843 }
3844 if (vsn) {
3845 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3846 if (!isttest(chk->send.http.vsn)) {
3847 memprintf(errmsg, "out of memory");
3848 goto error;
3849 }
3850 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003851 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003852 hdr = calloc(1, sizeof(*hdr));
3853 if (!hdr) {
3854 memprintf(errmsg, "out of memory");
3855 goto error;
3856 }
3857 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003858 hdr->name = istdup(hdrs[i].n);
3859 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003860 memprintf(errmsg, "out of memory");
3861 goto error;
3862 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003863
Christopher Fauletb61caf42020-04-21 10:57:42 +02003864 ist0(hdrs[i].v);
3865 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 +02003866 goto error;
3867 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3868 hdr = NULL;
3869 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003870
Christopher Faulet61cc8522020-04-20 14:54:42 +02003871 if (body) {
3872 chk->send.http.body = ist2(strdup(body), strlen(body));
3873 if (!isttest(chk->send.http.body)) {
3874 memprintf(errmsg, "out of memory");
3875 goto error;
3876 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003877 }
3878
Christopher Faulet61cc8522020-04-20 14:54:42 +02003879 return chk;
3880
3881 error:
3882 free_tcpcheck_http_hdr(hdr);
3883 free_tcpcheck(chk, 0);
3884 free(comment);
3885 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003886}
3887
Christopher Faulet61cc8522020-04-20 14:54:42 +02003888/* Parses and creates a http-check comment rule. NULL is returned on error */
3889static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3890 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003891{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003892 struct tcpcheck_rule *chk = NULL;
3893 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003894
Christopher Faulet61cc8522020-04-20 14:54:42 +02003895 if (!*(args[cur_arg+1])) {
3896 memprintf(errmsg, "expects a string as argument");
3897 goto error;
3898 }
3899 cur_arg++;
3900 comment = strdup(args[cur_arg]);
3901 if (!comment) {
3902 memprintf(errmsg, "out of memory");
3903 goto error;
3904 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003905
Christopher Faulet61cc8522020-04-20 14:54:42 +02003906 chk = calloc(1, sizeof(*chk));
3907 if (!chk) {
3908 memprintf(errmsg, "out of memory");
3909 goto error;
3910 }
3911 chk->action = TCPCHK_ACT_COMMENT;
3912 chk->comment = comment;
3913 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003914
Christopher Faulet61cc8522020-04-20 14:54:42 +02003915 error:
3916 free(comment);
3917 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003918}
3919
Christopher Faulet61cc8522020-04-20 14:54:42 +02003920/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3921 * on error. <proto> is set to the right protocol flags (covered by the
3922 * TCPCHK_RULES_PROTO_CHK mask).
3923 */
3924static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3925 struct list *rules, unsigned int proto,
3926 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003927{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003928 struct tcpcheck_rule *prev_check, *chk = NULL;
3929 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003930 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003931 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3932 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3933 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3934 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3935 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003936 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003937
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003938 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003939 if (!*(args[cur_arg+1])) {
3940 memprintf(errmsg, "expects at least a matching pattern as arguments");
3941 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003942 }
3943
Christopher Faulet61cc8522020-04-20 14:54:42 +02003944 cur_arg++;
3945 while (*(args[cur_arg])) {
3946 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003947
Christopher Faulet61cc8522020-04-20 14:54:42 +02003948 rescan:
3949 if (strcmp(args[cur_arg], "min-recv") == 0) {
3950 if (in_pattern) {
3951 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3952 goto error;
3953 }
3954 if (!*(args[cur_arg+1])) {
3955 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3956 goto error;
3957 }
3958 /* Use an signed integer here because of chksize */
3959 cur_arg++;
3960 min_recv = atol(args[cur_arg]);
3961 if (min_recv < -1 || min_recv > INT_MAX) {
3962 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3963 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003964 }
3965 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003966 else if (*(args[cur_arg]) == '!') {
3967 in_pattern = 1;
3968 while (*(args[cur_arg]) == '!') {
3969 inverse = !inverse;
3970 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003971 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003972 if (!*(args[cur_arg]))
3973 cur_arg++;
3974 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003975 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003976 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3977 if (type != TCPCHK_EXPECT_UNDEF) {
3978 memprintf(errmsg, "only on pattern expected");
3979 goto error;
3980 }
3981 if (proto != TCPCHK_RULES_HTTP_CHK)
3982 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3983 else
3984 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003985
Christopher Faulet61cc8522020-04-20 14:54:42 +02003986 if (!*(args[cur_arg+1])) {
3987 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3988 goto error;
3989 }
3990 cur_arg++;
3991 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003992 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003993 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3994 if (proto == TCPCHK_RULES_HTTP_CHK)
3995 goto bad_http_kw;
3996 if (type != TCPCHK_EXPECT_UNDEF) {
3997 memprintf(errmsg, "only on pattern expected");
3998 goto error;
3999 }
4000 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004001
Christopher Faulet61cc8522020-04-20 14:54:42 +02004002 if (!*(args[cur_arg+1])) {
4003 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4004 goto error;
4005 }
4006 cur_arg++;
4007 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004008 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004009 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4010 if (proto != TCPCHK_RULES_HTTP_CHK)
4011 goto bad_tcp_kw;
4012 if (type != TCPCHK_EXPECT_UNDEF) {
4013 memprintf(errmsg, "only on pattern expected");
4014 goto error;
4015 }
4016 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004017
Christopher Faulet61cc8522020-04-20 14:54:42 +02004018 if (!*(args[cur_arg+1])) {
4019 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4020 goto error;
4021 }
4022 cur_arg++;
4023 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004024 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004025 else if (strcmp(args[cur_arg], "custom") == 0) {
4026 if (in_pattern) {
4027 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4028 goto error;
4029 }
4030 if (type != TCPCHK_EXPECT_UNDEF) {
4031 memprintf(errmsg, "only on pattern expected");
4032 goto error;
4033 }
4034 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004035 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004036 else if (strcmp(args[cur_arg], "comment") == 0) {
4037 if (in_pattern) {
4038 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4039 goto error;
4040 }
4041 if (!*(args[cur_arg+1])) {
4042 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4043 goto error;
4044 }
4045 cur_arg++;
4046 free(comment);
4047 comment = strdup(args[cur_arg]);
4048 if (!comment) {
4049 memprintf(errmsg, "out of memory");
4050 goto error;
4051 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004052 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004053 else if (strcmp(args[cur_arg], "on-success") == 0) {
4054 if (in_pattern) {
4055 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4056 goto error;
4057 }
4058 if (!*(args[cur_arg+1])) {
4059 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4060 goto error;
4061 }
4062 cur_arg++;
4063 free(on_success_msg);
4064 on_success_msg = strdup(args[cur_arg]);
4065 if (!on_success_msg) {
4066 memprintf(errmsg, "out of memory");
4067 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004068 }
4069 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004070 else if (strcmp(args[cur_arg], "on-error") == 0) {
4071 if (in_pattern) {
4072 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4073 goto error;
4074 }
4075 if (!*(args[cur_arg+1])) {
4076 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4077 goto error;
4078 }
4079 cur_arg++;
4080 free(on_error_msg);
4081 on_error_msg = strdup(args[cur_arg]);
4082 if (!on_error_msg) {
4083 memprintf(errmsg, "out of memory");
4084 goto error;
4085 }
4086 }
4087 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4088 if (in_pattern) {
4089 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4090 goto error;
4091 }
4092 if (!*(args[cur_arg+1])) {
4093 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4094 goto error;
4095 }
4096 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4097 ok_st = HCHK_STATUS_L7OKD;
4098 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4099 ok_st = HCHK_STATUS_L7OKCD;
4100 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4101 ok_st = HCHK_STATUS_L6OK;
4102 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4103 ok_st = HCHK_STATUS_L4OK;
4104 else {
4105 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4106 args[cur_arg], args[cur_arg+1]);
4107 goto error;
4108 }
4109 cur_arg++;
4110 }
4111 else if (strcmp(args[cur_arg], "error-status") == 0) {
4112 if (in_pattern) {
4113 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4114 goto error;
4115 }
4116 if (!*(args[cur_arg+1])) {
4117 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4118 goto error;
4119 }
4120 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4121 err_st = HCHK_STATUS_L7RSP;
4122 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4123 err_st = HCHK_STATUS_L7STS;
4124 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4125 err_st = HCHK_STATUS_L6RSP;
4126 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4127 err_st = HCHK_STATUS_L4CON;
4128 else {
4129 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4130 args[cur_arg], args[cur_arg+1]);
4131 goto error;
4132 }
4133 cur_arg++;
4134 }
4135 else if (strcmp(args[cur_arg], "status-code") == 0) {
4136 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004137
Christopher Faulet61cc8522020-04-20 14:54:42 +02004138 if (in_pattern) {
4139 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4140 goto error;
4141 }
4142 if (!*(args[cur_arg+1])) {
4143 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4144 goto error;
4145 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004146
Christopher Faulet61cc8522020-04-20 14:54:42 +02004147 cur_arg++;
4148 release_sample_expr(status_expr);
4149 px->conf.args.ctx = ARGC_SRV;
4150 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4151 file, line, errmsg, &px->conf.args, NULL);
4152 if (!status_expr) {
4153 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4154 goto error;
4155 }
4156 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4157 memprintf(errmsg, "error detected while parsing status-code expression : "
4158 " fetch method '%s' extracts information from '%s', "
4159 "none of which is available here.\n",
4160 args[cur_arg], sample_src_names(status_expr->fetch->use));
4161 goto error;
4162 }
4163 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4164 }
4165 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4166 if (in_pattern) {
4167 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4168 goto error;
4169 }
4170 if (!*(args[cur_arg+1])) {
4171 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4172 goto error;
4173 }
4174 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4175 tout_st = HCHK_STATUS_L7TOUT;
4176 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4177 tout_st = HCHK_STATUS_L6TOUT;
4178 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4179 tout_st = HCHK_STATUS_L4TOUT;
4180 else {
4181 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4182 args[cur_arg], args[cur_arg+1]);
4183 goto error;
4184 }
4185 cur_arg++;
4186 }
4187 else {
4188 if (proto == TCPCHK_RULES_HTTP_CHK) {
4189 bad_http_kw:
4190 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4191 " or comment but got '%s' as argument.", args[cur_arg]);
4192 }
4193 else {
4194 bad_tcp_kw:
4195 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4196 " or comment but got '%s' as argument.", args[cur_arg]);
4197 }
4198 goto error;
4199 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004200
Christopher Faulet61cc8522020-04-20 14:54:42 +02004201 cur_arg++;
4202 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004203
Christopher Faulet61cc8522020-04-20 14:54:42 +02004204 chk = calloc(1, sizeof(*chk));
4205 if (!chk) {
4206 memprintf(errmsg, "out of memory");
4207 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004208 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004209 chk->action = TCPCHK_ACT_EXPECT;
4210 LIST_INIT(&chk->expect.onerror_fmt);
4211 LIST_INIT(&chk->expect.onsuccess_fmt);
4212 chk->comment = comment; comment = NULL;
4213 chk->expect.type = type;
4214 chk->expect.min_recv = min_recv;
4215 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004216 chk->expect.ok_status = ok_st;
4217 chk->expect.err_status = err_st;
4218 chk->expect.tout_status = tout_st;
4219 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004220
Christopher Faulet61cc8522020-04-20 14:54:42 +02004221 if (on_success_msg) {
4222 px->conf.args.ctx = ARGC_SRV;
4223 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4224 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4225 goto error;
4226 }
4227 free(on_success_msg);
4228 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004229 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004230 if (on_error_msg) {
4231 px->conf.args.ctx = ARGC_SRV;
4232 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4233 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4234 goto error;
4235 }
4236 free(on_error_msg);
4237 on_error_msg = NULL;
4238 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004239
Christopher Faulet61cc8522020-04-20 14:54:42 +02004240 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004241 case TCPCHK_EXPECT_HTTP_STATUS: {
4242 const char *p = pattern;
4243 unsigned int c1,c2;
4244
4245 chk->expect.codes.codes = NULL;
4246 chk->expect.codes.num = 0;
4247 while (1) {
4248 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4249 if (*p == '-') {
4250 p++;
4251 c2 = read_uint(&p, pattern + strlen(pattern));
4252 }
4253 if (c1 > c2) {
4254 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4255 goto error;
4256 }
4257
4258 chk->expect.codes.num++;
4259 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4260 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4261 if (!chk->expect.codes.codes) {
4262 memprintf(errmsg, "out of memory");
4263 goto error;
4264 }
4265 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4266 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4267
4268 if (*p == '\0')
4269 break;
4270 if (*p != ',') {
4271 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4272 goto error;
4273 }
4274 p++;
4275 }
4276 break;
4277 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004278 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004279 case TCPCHK_EXPECT_HTTP_BODY:
4280 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004281 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004282 memprintf(errmsg, "out of memory");
4283 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004284 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004285 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004286 case TCPCHK_EXPECT_BINARY: {
4287 int len = 0;
4288
4289 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004290 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4291 goto error;
4292 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004293 chk->expect.data.len = len;
4294 break;
4295 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004296 case TCPCHK_EXPECT_REGEX:
4297 case TCPCHK_EXPECT_REGEX_BINARY:
4298 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4299 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004300 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004301 if (!chk->expect.regex)
4302 goto error;
4303 break;
4304 case TCPCHK_EXPECT_CUSTOM:
4305 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4306 break;
4307 case TCPCHK_EXPECT_UNDEF:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004308 memprintf(errmsg, "pattern not found");
4309 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004310 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004311
Christopher Faulet61cc8522020-04-20 14:54:42 +02004312 /* All tcp-check expect points back to the first inverse expect rule in
4313 * a chain of one or more expect rule, potentially itself.
4314 */
4315 chk->expect.head = chk;
4316 list_for_each_entry_rev(prev_check, rules, list) {
4317 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4318 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4319 chk->expect.head = prev_check;
4320 continue;
4321 }
4322 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4323 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004324 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004325 return chk;
4326
4327 error:
4328 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004329 free(comment);
4330 free(on_success_msg);
4331 free(on_error_msg);
4332 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004333 return NULL;
4334}
4335
Christopher Faulet61cc8522020-04-20 14:54:42 +02004336/* Overwrites fields of the old http send rule with those of the new one. When
4337 * replaced, old values are freed and replaced by the new ones. New values are
4338 * not copied but transferred. At the end <new> should be empty and can be
4339 * safely released. This function never fails.
4340 */
4341static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004342{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004343 struct logformat_node *lf, *lfb;
4344 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004345
Christopher Faulet404f9192020-04-09 23:13:54 +02004346
Christopher Faulet61cc8522020-04-20 14:54:42 +02004347 if (new->send.http.meth.str.area) {
4348 free(old->send.http.meth.str.area);
4349 old->send.http.meth.meth = new->send.http.meth.meth;
4350 old->send.http.meth.str.area = new->send.http.meth.str.area;
4351 old->send.http.meth.str.data = new->send.http.meth.str.data;
4352 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004353 }
4354
Christopher Faulet61cc8522020-04-20 14:54:42 +02004355 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4356 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004357 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004358 else
4359 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4360 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4361 old->send.http.uri = new->send.http.uri;
4362 new->send.http.uri = IST_NULL;
4363 }
4364 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4365 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004366 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004367 else
4368 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4369 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4370 LIST_INIT(&old->send.http.uri_fmt);
4371 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4372 LIST_DEL(&lf->list);
4373 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4374 }
4375 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004376
Christopher Faulet61cc8522020-04-20 14:54:42 +02004377 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004378 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004379 old->send.http.vsn = new->send.http.vsn;
4380 new->send.http.vsn = IST_NULL;
4381 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004382
Christopher Faulet61cc8522020-04-20 14:54:42 +02004383 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4384 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4385 LIST_DEL(&hdr->list);
4386 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004387 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004388
4389 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4390 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004391 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004392 else
4393 free_tcpcheck_fmt(&old->send.http.body_fmt);
4394 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4395 old->send.http.body = new->send.http.body;
4396 new->send.http.body = IST_NULL;
4397 }
4398 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4399 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004400 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004401 else
4402 free_tcpcheck_fmt(&old->send.http.body_fmt);
4403 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4404 LIST_INIT(&old->send.http.body_fmt);
4405 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4406 LIST_DEL(&lf->list);
4407 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4408 }
4409 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004410}
4411
Christopher Faulet61cc8522020-04-20 14:54:42 +02004412/* Internal function used to add an http-check rule in a list during the config
4413 * parsing step. Depending on its type, and the previously inserted rules, a
4414 * specific action may be performed or an error may be reported. This functions
4415 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4416 * message.
4417 */
4418static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004419{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004420 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004421
Christopher Faulet61cc8522020-04-20 14:54:42 +02004422 /* the implicit send rule coming from an "option httpchk" line must be
4423 * merged with the first explici http-check send rule, if
4424 * any. Depdending the declaration order some tests are required.
4425 *
4426 * Some tests is also required for other kinds of http-check rules to be
4427 * sure the ruleset remains valid.
4428 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004429
Christopher Faulet61cc8522020-04-20 14:54:42 +02004430 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4431 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4432 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4433 * following tests are performed :
4434 *
4435 * 1- If there is no such rule or if it is not a send rule, the implicit send
4436 * rule is pushed in front of the ruleset
4437 *
4438 * 2- If it is another implicit send rule, it is replaced with the new one.
4439 *
4440 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4441 * both, overwritting the old send rule (the explicit one) with info of the
4442 * new send rule (the implicit one).
4443 */
4444 r = get_first_tcpcheck_rule(rules);
4445 if (r && r->action == TCPCHK_ACT_CONNECT)
4446 r = get_next_tcpcheck_rule(rules, r);
4447 if (!r || r->action != TCPCHK_ACT_SEND)
4448 LIST_ADD(rules->list, &chk->list);
4449 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4450 LIST_DEL(&r->list);
4451 free_tcpcheck(r, 0);
4452 LIST_ADD(rules->list, &chk->list);
4453 }
4454 else {
4455 tcpcheck_overwrite_send_http_rule(r, chk);
4456 free_tcpcheck(chk, 0);
4457 }
4458 }
4459 else {
4460 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4461 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4462 * with an existing implicit send rule, if any. At the end, if there is no error,
4463 * the rule is appended to the list.
4464 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004465
Christopher Faulet61cc8522020-04-20 14:54:42 +02004466 r = get_last_tcpcheck_rule(rules);
4467 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4468 /* no error */;
4469 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4470 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4471 chk->index+1);
4472 return 0;
4473 }
4474 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4475 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4476 chk->index+1);
4477 return 0;
4478 }
4479 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4480 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4481 chk->index+1);
4482 return 0;
4483 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004484
Christopher Faulet61cc8522020-04-20 14:54:42 +02004485 if (chk->action == TCPCHK_ACT_SEND) {
4486 r = get_first_tcpcheck_rule(rules);
4487 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4488 tcpcheck_overwrite_send_http_rule(r, chk);
4489 free_tcpcheck(chk, 0);
4490 LIST_DEL(&r->list);
4491 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4492 chk = r;
4493 }
4494 }
4495 LIST_ADDQ(rules->list, &chk->list);
4496 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004497 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004498}
4499
Christopher Faulet61cc8522020-04-20 14:54:42 +02004500/**************************************************************************/
4501/************************** Init/deinit checks ****************************/
4502/**************************************************************************/
4503static const char *init_check(struct check *check, int type)
4504{
4505 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004506
Christopher Faulet61cc8522020-04-20 14:54:42 +02004507 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4508 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004509
Christopher Faulet61cc8522020-04-20 14:54:42 +02004510 check->bi.area = calloc(check->bi.size, sizeof(char));
4511 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004512
Christopher Faulet61cc8522020-04-20 14:54:42 +02004513 if (!check->bi.area || !check->bo.area)
4514 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004515
Christopher Faulet61cc8522020-04-20 14:54:42 +02004516 check->wait_list.tasklet = tasklet_new();
4517 if (!check->wait_list.tasklet)
4518 return "out of memory while allocating check tasklet";
4519 check->wait_list.events = 0;
4520 check->wait_list.tasklet->process = event_srv_chk_io;
4521 check->wait_list.tasklet->context = check;
4522 return NULL;
4523}
4524
4525void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004526{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004527 task_destroy(check->task);
4528 if (check->wait_list.tasklet)
4529 tasklet_free(check->wait_list.tasklet);
4530
4531 free(check->bi.area);
4532 free(check->bo.area);
4533 if (check->cs) {
4534 free(check->cs->conn);
4535 check->cs->conn = NULL;
4536 cs_free(check->cs);
4537 check->cs = NULL;
4538 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004539}
4540
Christopher Faulet61cc8522020-04-20 14:54:42 +02004541/* manages a server health-check. Returns the time the task accepts to wait, or
4542 * TIME_ETERNITY for infinity.
4543 */
4544static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004545{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004546 struct check *check = context;
4547
4548 if (check->type == PR_O2_EXT_CHK)
4549 return process_chk_proc(t, context, state);
4550 return process_chk_conn(t, context, state);
4551
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004552}
4553
Christopher Faulet61cc8522020-04-20 14:54:42 +02004554
4555static int start_check_task(struct check *check, int mininter,
4556 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004557{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004558 struct task *t;
4559 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004560
Christopher Faulet61cc8522020-04-20 14:54:42 +02004561 if (check->type == PR_O2_EXT_CHK)
4562 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004563
Christopher Faulet61cc8522020-04-20 14:54:42 +02004564 /* task for the check */
4565 if ((t = task_new(thread_mask)) == NULL) {
4566 ha_alert("Starting [%s:%s] check: out of memory.\n",
4567 check->server->proxy->id, check->server->id);
4568 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004569 }
4570
Christopher Faulet61cc8522020-04-20 14:54:42 +02004571 check->task = t;
4572 t->process = process_chk;
4573 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004574
Christopher Faulet61cc8522020-04-20 14:54:42 +02004575 if (mininter < srv_getinter(check))
4576 mininter = srv_getinter(check);
4577
4578 if (global.max_spread_checks && mininter > global.max_spread_checks)
4579 mininter = global.max_spread_checks;
4580
4581 /* check this every ms */
4582 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4583 check->start = now;
4584 task_queue(t);
4585
4586 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004587}
4588
Christopher Faulet61cc8522020-04-20 14:54:42 +02004589/* updates the server's weight during a warmup stage. Once the final weight is
4590 * reached, the task automatically stops. Note that any server status change
4591 * must have updated s->last_change accordingly.
4592 */
4593static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004594{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004595 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004596
Christopher Faulet61cc8522020-04-20 14:54:42 +02004597 /* by default, plan on stopping the task */
4598 t->expire = TICK_ETERNITY;
4599 if ((s->next_admin & SRV_ADMF_MAINT) ||
4600 (s->next_state != SRV_ST_STARTING))
4601 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004602
Christopher Faulet61cc8522020-04-20 14:54:42 +02004603 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004604
Christopher Faulet61cc8522020-04-20 14:54:42 +02004605 /* recalculate the weights and update the state */
4606 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004607
Christopher Faulet61cc8522020-04-20 14:54:42 +02004608 /* probably that we can refill this server with a bit more connections */
4609 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004610
Christopher Faulet61cc8522020-04-20 14:54:42 +02004611 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004612
Christopher Faulet61cc8522020-04-20 14:54:42 +02004613 /* get back there in 1 second or 1/20th of the slowstart interval,
4614 * whichever is greater, resulting in small 5% steps.
4615 */
4616 if (s->next_state == SRV_ST_STARTING)
4617 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4618 return t;
4619}
4620
4621/*
4622 * Start health-check.
4623 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4624 */
4625static int start_checks()
4626{
4627
4628 struct proxy *px;
4629 struct server *s;
4630 struct task *t;
4631 int nbcheck=0, mininter=0, srvpos=0;
4632
4633 /* 0- init the dummy frontend used to create all checks sessions */
4634 init_new_proxy(&checks_fe);
4635 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4636 checks_fe.mode = PR_MODE_TCP;
4637 checks_fe.maxconn = 0;
4638 checks_fe.conn_retries = CONN_RETRIES;
4639 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4640 checks_fe.timeout.client = TICK_ETERNITY;
4641
4642 /* 1- count the checkers to run simultaneously.
4643 * We also determine the minimum interval among all of those which
4644 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4645 * will be used to spread their start-up date. Those which have
4646 * a shorter interval will start independently and will not dictate
4647 * too short an interval for all others.
4648 */
4649 for (px = proxies_list; px; px = px->next) {
4650 for (s = px->srv; s; s = s->next) {
4651 if (s->slowstart) {
4652 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4653 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4654 return ERR_ALERT | ERR_FATAL;
4655 }
4656 /* We need a warmup task that will be called when the server
4657 * state switches from down to up.
4658 */
4659 s->warmup = t;
4660 t->process = server_warmup;
4661 t->context = s;
4662 /* server can be in this state only because of */
4663 if (s->next_state == SRV_ST_STARTING)
4664 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 +02004665 }
4666
Christopher Faulet61cc8522020-04-20 14:54:42 +02004667 if (s->check.state & CHK_ST_CONFIGURED) {
4668 nbcheck++;
4669 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4670 (!mininter || mininter > srv_getinter(&s->check)))
4671 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004672 }
4673
Christopher Faulet61cc8522020-04-20 14:54:42 +02004674 if (s->agent.state & CHK_ST_CONFIGURED) {
4675 nbcheck++;
4676 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4677 (!mininter || mininter > srv_getinter(&s->agent)))
4678 mininter = srv_getinter(&s->agent);
4679 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004680 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004681 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004682
Christopher Faulet61cc8522020-04-20 14:54:42 +02004683 if (!nbcheck)
4684 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004685
Christopher Faulet61cc8522020-04-20 14:54:42 +02004686 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004687
Christopher Faulet61cc8522020-04-20 14:54:42 +02004688 /*
4689 * 2- start them as far as possible from each others. For this, we will
4690 * start them after their interval set to the min interval divided by
4691 * the number of servers, weighted by the server's position in the list.
4692 */
4693 for (px = proxies_list; px; px = px->next) {
4694 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4695 if (init_pid_list()) {
4696 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4697 return ERR_ALERT | ERR_FATAL;
4698 }
4699 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004700
Christopher Faulet61cc8522020-04-20 14:54:42 +02004701 for (s = px->srv; s; s = s->next) {
4702 /* A task for the main check */
4703 if (s->check.state & CHK_ST_CONFIGURED) {
4704 if (s->check.type == PR_O2_EXT_CHK) {
4705 if (!prepare_external_check(&s->check))
4706 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004707 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4709 return ERR_ALERT | ERR_FATAL;
4710 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004711 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004712
Christopher Faulet61cc8522020-04-20 14:54:42 +02004713 /* A task for a auxiliary agent check */
4714 if (s->agent.state & CHK_ST_CONFIGURED) {
4715 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4716 return ERR_ALERT | ERR_FATAL;
4717 }
4718 srvpos++;
4719 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004720 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004721 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004722 return 0;
4723}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004724
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004725
Christopher Faulet61cc8522020-04-20 14:54:42 +02004726/*
4727 * Return value:
4728 * the port to be used for the health check
4729 * 0 in case no port could be found for the check
4730 */
4731static int srv_check_healthcheck_port(struct check *chk)
4732{
4733 int i = 0;
4734 struct server *srv = NULL;
4735
4736 srv = chk->server;
4737
4738 /* by default, we use the health check port ocnfigured */
4739 if (chk->port > 0)
4740 return chk->port;
4741
4742 /* try to get the port from check_core.addr if check.port not set */
4743 i = get_host_port(&chk->addr);
4744 if (i > 0)
4745 return i;
4746
4747 /* try to get the port from server address */
4748 /* prevent MAPPORTS from working at this point, since checks could
4749 * not be performed in such case (MAPPORTS impose a relative ports
4750 * based on live traffic)
4751 */
4752 if (srv->flags & SRV_F_MAPPORTS)
4753 return 0;
4754
4755 i = srv->svc_port; /* by default */
4756 if (i > 0)
4757 return i;
4758
4759 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004760}
4761
Christopher Faulet61cc8522020-04-20 14:54:42 +02004762/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4763 * if an error occurred.
4764 */
4765static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004766{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004767 const char *err;
4768 struct tcpcheck_rule *r;
4769 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004770
Christopher Faulet61cc8522020-04-20 14:54:42 +02004771 if (!srv->do_check)
4772 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004773
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004774
Christopher Faulet61cc8522020-04-20 14:54:42 +02004775 /* If neither a port nor an addr was specified and no check transport
4776 * layer is forced, then the transport layer used by the checks is the
4777 * same as for the production traffic. Otherwise we use raw_sock by
4778 * default, unless one is specified.
4779 */
4780 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4781 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4782 srv->check.use_ssl = srv->use_ssl;
4783 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004784 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004785 else if (srv->check.use_ssl == 1)
4786 srv->check.xprt = xprt_get(XPRT_SSL);
4787 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004788 }
4789
Christopher Faulet12882cf2020-04-23 15:50:18 +02004790 /* Inherit the mux protocol from the server if not already defined for
4791 * the check
4792 */
4793 if (srv->mux_proto && !srv->check.mux_proto)
4794 srv->check.mux_proto = srv->mux_proto;
4795
Christopher Faulet61cc8522020-04-20 14:54:42 +02004796 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004797
Christopher Faulet61cc8522020-04-20 14:54:42 +02004798 /* We need at least a service port, a check port or the first tcp-check
4799 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4800 */
4801 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4802 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4803 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004804
Christopher Faulet61cc8522020-04-20 14:54:42 +02004805 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4806 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\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 /* search the first action (connect / send / expect) in the list */
4813 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4814 if (!r || (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 "nor tcp_check rule 'connect' with port information.\n",
4817 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4818 ret |= ERR_ALERT | ERR_ABORT;
4819 goto out;
4820 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004821
Christopher Faulet61cc8522020-04-20 14:54:42 +02004822 /* scan the tcp-check ruleset to ensure a port has been configured */
4823 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4824 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4825 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4826 "and a tcp_check rule 'connect' with no port information.\n",
4827 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4828 ret |= ERR_ALERT | ERR_ABORT;
4829 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004830 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004831 }
4832
Christopher Faulet61cc8522020-04-20 14:54:42 +02004833 init:
4834 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4835 struct tcpcheck_ruleset *rs = NULL;
4836 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4837 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004838
Christopher Faulet61cc8522020-04-20 14:54:42 +02004839 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4840 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004841
Christopher Faulet61cc8522020-04-20 14:54:42 +02004842 rs = find_tcpcheck_ruleset("*tcp-check");
4843 if (!rs) {
4844 rs = create_tcpcheck_ruleset("*tcp-check");
4845 if (rs == NULL) {
4846 ha_alert("config: %s '%s': out of memory.\n",
4847 proxy_type_str(srv->proxy), srv->proxy->id);
4848 ret |= ERR_ALERT | ERR_FATAL;
4849 goto out;
4850 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004851 }
4852
Christopher Faulet61cc8522020-04-20 14:54:42 +02004853 free_tcpcheck_vars(&rules->preset_vars);
4854 rules->list = &rs->rules;
4855 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004856 }
4857
Christopher Faulet61cc8522020-04-20 14:54:42 +02004858 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4859 if (err) {
4860 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4861 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4862 ret |= ERR_ALERT | ERR_ABORT;
4863 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004864 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004865 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4866 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004867
Christopher Faulet61cc8522020-04-20 14:54:42 +02004868 out:
4869 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004870}
4871
Christopher Faulet61cc8522020-04-20 14:54:42 +02004872/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4873 * if an error occurred.
4874 */
4875static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004876{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004877 struct tcpcheck_rule *chk;
4878 const char *err;
4879 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004880
Christopher Faulet61cc8522020-04-20 14:54:42 +02004881 if (!srv->do_agent)
4882 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004883
Christopher Faulet61cc8522020-04-20 14:54:42 +02004884 /* If there is no connect rule preceeding all send / expect rules, an
4885 * implicit one is inserted before all others.
4886 */
4887 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4888 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4889 chk = calloc(1, sizeof(*chk));
4890 if (!chk) {
4891 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4892 " to agent-check for server '%s' (out of memory).\n",
4893 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4894 ret |= ERR_ALERT | ERR_FATAL;
4895 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004896 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004897 chk->action = TCPCHK_ACT_CONNECT;
4898 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4899 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004900 }
4901
Christopher Faulete5870d82020-04-15 11:32:03 +02004902
Christopher Faulet61cc8522020-04-20 14:54:42 +02004903 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4904 if (err) {
4905 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4906 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4907 ret |= ERR_ALERT | ERR_ABORT;
4908 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004909 }
4910
Christopher Faulet61cc8522020-04-20 14:54:42 +02004911 if (!srv->agent.inter)
4912 srv->agent.inter = srv->check.inter;
4913
4914 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4915 global.maxsock++;
4916
4917 out:
4918 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004919}
4920
Christopher Faulet61cc8522020-04-20 14:54:42 +02004921/* Check tcp-check health-check configuration for the proxy <px>. */
4922static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004923{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004924 struct tcpcheck_rule *chk, *back;
4925 char *comment = NULL, *errmsg = NULL;
4926 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4927 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004928
Christopher Faulet61cc8522020-04-20 14:54:42 +02004929 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4930 deinit_proxy_tcpcheck(px);
4931 goto out;
4932 }
4933
4934 free(px->check_command);
4935 free(px->check_path);
4936 px->check_command = px->check_path = NULL;
4937
4938 if (!px->tcpcheck_rules.list) {
4939 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4940 ret |= ERR_ALERT | ERR_FATAL;
4941 goto out;
4942 }
4943
4944 /* HTTP ruleset only : */
4945 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4946 struct tcpcheck_rule *next;
4947
4948 /* move remaining implicit send rule from "option httpchk" line to the right place.
4949 * If such rule exists, it must be the first one. In this case, the rule is moved
4950 * after the first connect rule, if any. Otherwise, nothing is done.
4951 */
4952 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4953 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4954 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4955 if (next && next->action == TCPCHK_ACT_CONNECT) {
4956 LIST_DEL(&chk->list);
4957 LIST_ADD(&next->list, &chk->list);
4958 chk->index = next->index;
4959 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004960 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004961
4962 /* add implicit expect rule if the last one is a send. It is inherited from previous
4963 * versions where the http expect rule was optional. Now it is possible to chained
4964 * send/expect rules but the last expect may still be implicit.
4965 */
4966 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4967 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004968 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004969 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4970 px->conf.file, px->conf.line, &errmsg);
4971 if (!next) {
4972 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4973 "(%s).\n", px->id, errmsg);
4974 free(errmsg);
4975 ret |= ERR_ALERT | ERR_FATAL;
4976 goto out;
4977 }
4978 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4979 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004980 }
4981 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004982
4983 /* For all ruleset: */
4984
4985 /* If there is no connect rule preceeding all send / expect rules, an
4986 * implicit one is inserted before all others.
4987 */
4988 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4989 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4990 chk = calloc(1, sizeof(*chk));
4991 if (!chk) {
4992 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4993 "(out of memory).\n", px->id);
4994 ret |= ERR_ALERT | ERR_FATAL;
4995 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004996 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004997 chk->action = TCPCHK_ACT_CONNECT;
4998 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4999 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
5000 }
5001
5002 /* Remove all comment rules. To do so, when a such rule is found, the
5003 * comment is assigned to the following rule(s).
5004 */
5005 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5006 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5007 free(comment);
5008 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005009 }
5010
Christopher Faulet61cc8522020-04-20 14:54:42 +02005011 prev_action = chk->action;
5012 switch (chk->action) {
5013 case TCPCHK_ACT_COMMENT:
5014 free(comment);
5015 comment = chk->comment;
5016 LIST_DEL(&chk->list);
5017 free(chk);
5018 break;
5019 case TCPCHK_ACT_CONNECT:
5020 if (!chk->comment && comment)
5021 chk->comment = strdup(comment);
5022 /* fall though */
5023 case TCPCHK_ACT_ACTION_KW:
5024 free(comment);
5025 comment = NULL;
5026 break;
5027 case TCPCHK_ACT_SEND:
5028 case TCPCHK_ACT_EXPECT:
5029 if (!chk->comment && comment)
5030 chk->comment = strdup(comment);
5031 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005032 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005033 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005034 free(comment);
5035 comment = NULL;
5036
5037 out:
5038 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005039}
5040
Christopher Faulet61cc8522020-04-20 14:54:42 +02005041void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005042{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005043 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5044 px->tcpcheck_rules.flags = 0;
5045 px->tcpcheck_rules.list = NULL;
5046}
Christopher Faulete5870d82020-04-15 11:32:03 +02005047
Christopher Faulet61cc8522020-04-20 14:54:42 +02005048static void deinit_srv_check(struct server *srv)
5049{
5050 if (srv->check.state & CHK_ST_CONFIGURED)
5051 free_check(&srv->check);
5052 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5053 srv->do_check = 0;
5054}
Christopher Faulete5870d82020-04-15 11:32:03 +02005055
Christopher Faulet61cc8522020-04-20 14:54:42 +02005056
5057static void deinit_srv_agent_check(struct server *srv)
5058{
5059 if (srv->agent.tcpcheck_rules) {
5060 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5061 free(srv->agent.tcpcheck_rules);
5062 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005063 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005064
Christopher Faulet61cc8522020-04-20 14:54:42 +02005065 if (srv->agent.state & CHK_ST_CONFIGURED)
5066 free_check(&srv->agent);
5067
5068 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5069 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005070}
5071
Christopher Faulet61cc8522020-04-20 14:54:42 +02005072static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005073{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005074 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005075 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005076 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005077
Christopher Fauletd7cee712020-04-21 13:45:00 +02005078 node = ebpt_first(&shared_tcpchecks);
5079 while (node) {
5080 next = ebpt_next(node);
5081 ebpt_delete(node);
5082 free(node->key);
5083 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005084 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5085 LIST_DEL(&r->list);
5086 free_tcpcheck(r, 0);
5087 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005088 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005089 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005090 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005091}
Christopher Faulete5870d82020-04-15 11:32:03 +02005092
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005093
Christopher Faulet61cc8522020-04-20 14:54:42 +02005094REGISTER_POST_SERVER_CHECK(init_srv_check);
5095REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5096REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5097REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005098
Christopher Faulet61cc8522020-04-20 14:54:42 +02005099REGISTER_SERVER_DEINIT(deinit_srv_check);
5100REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5101REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5102REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005103
Christopher Faulet61cc8522020-04-20 14:54:42 +02005104/**************************************************************************/
5105/****************************** Email alerts ******************************/
5106/* NOTE: It may be pertinent to use an applet to handle email alerts */
5107/* instead of a tcp-check ruleset */
5108/**************************************************************************/
5109void email_alert_free(struct email_alert *alert)
5110{
5111 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005112
Christopher Faulet61cc8522020-04-20 14:54:42 +02005113 if (!alert)
5114 return;
5115
5116 if (alert->rules.list) {
5117 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5118 LIST_DEL(&rule->list);
5119 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005120 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005121 free_tcpcheck_vars(&alert->rules.preset_vars);
5122 free(alert->rules.list);
5123 alert->rules.list = NULL;
5124 }
5125 pool_free(pool_head_email_alert, alert);
5126}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005127
Christopher Faulet61cc8522020-04-20 14:54:42 +02005128static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5129{
5130 struct check *check = context;
5131 struct email_alertq *q;
5132 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005133
Christopher Faulet61cc8522020-04-20 14:54:42 +02005134 q = container_of(check, typeof(*q), check);
5135
5136 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5137 while (1) {
5138 if (!(check->state & CHK_ST_ENABLED)) {
5139 if (LIST_ISEMPTY(&q->email_alerts)) {
5140 /* All alerts processed, queue the task */
5141 t->expire = TICK_ETERNITY;
5142 task_queue(t);
5143 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005144 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005145
5146 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5147 LIST_DEL(&alert->list);
5148 t->expire = now_ms;
5149 check->tcpcheck_rules = &alert->rules;
5150 check->status = HCHK_STATUS_INI;
5151 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005152 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005153
5154 process_chk(t, context, state);
5155 if (check->state & CHK_ST_INPROGRESS)
5156 break;
5157
5158 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5159 email_alert_free(alert);
5160 check->tcpcheck_rules = NULL;
5161 check->server = NULL;
5162 check->state &= ~CHK_ST_ENABLED;
5163 }
5164 end:
5165 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5166 return t;
5167}
5168
5169/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5170 *
5171 * The function returns 1 in success case, otherwise, it returns 0 and err is
5172 * filled.
5173 */
5174int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5175{
5176 struct mailer *mailer;
5177 struct email_alertq *queues;
5178 const char *err_str;
5179 int i = 0;
5180
5181 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5182 memprintf(err, "out of memory while allocating mailer alerts queues");
5183 goto fail_no_queue;
5184 }
5185
5186 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5187 struct email_alertq *q = &queues[i];
5188 struct check *check = &q->check;
5189 struct task *t;
5190
5191 LIST_INIT(&q->email_alerts);
5192 HA_SPIN_INIT(&q->lock);
5193 check->inter = mls->timeout.mail;
5194 check->rise = DEF_AGENT_RISETIME;
5195 check->proxy = p;
5196 check->fall = DEF_AGENT_FALLTIME;
5197 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5198 memprintf(err, "%s", err_str);
5199 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005200 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005201
5202 check->xprt = mailer->xprt;
5203 check->addr = mailer->addr;
5204 check->port = get_host_port(&mailer->addr);
5205
5206 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5207 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005208 goto error;
5209 }
5210
Christopher Faulet61cc8522020-04-20 14:54:42 +02005211 check->task = t;
5212 t->process = process_email_alert;
5213 t->context = check;
5214
5215 /* check this in one ms */
5216 t->expire = TICK_ETERNITY;
5217 check->start = now;
5218 task_queue(t);
5219 }
5220
5221 mls->users++;
5222 free(p->email_alert.mailers.name);
5223 p->email_alert.mailers.m = mls;
5224 p->email_alert.queues = queues;
5225 return 0;
5226
5227 error:
5228 for (i = 0; i < mls->count; i++) {
5229 struct email_alertq *q = &queues[i];
5230 struct check *check = &q->check;
5231
5232 free_check(check);
5233 }
5234 free(queues);
5235 fail_no_queue:
5236 return 1;
5237}
5238
5239static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5240{
5241 struct tcpcheck_rule *tcpcheck, *prev_check;
5242 struct tcpcheck_expect *expect;
5243
5244 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5245 return 0;
5246 memset(tcpcheck, 0, sizeof(*tcpcheck));
5247 tcpcheck->action = TCPCHK_ACT_EXPECT;
5248
5249 expect = &tcpcheck->expect;
5250 expect->type = TCPCHK_EXPECT_STRING;
5251 LIST_INIT(&expect->onerror_fmt);
5252 LIST_INIT(&expect->onsuccess_fmt);
5253 expect->ok_status = HCHK_STATUS_L7OKD;
5254 expect->err_status = HCHK_STATUS_L7RSP;
5255 expect->tout_status = HCHK_STATUS_L7TOUT;
5256 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005257 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005258 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5259 return 0;
5260 }
5261
5262 /* All tcp-check expect points back to the first inverse expect rule
5263 * in a chain of one or more expect rule, potentially itself.
5264 */
5265 tcpcheck->expect.head = tcpcheck;
5266 list_for_each_entry_rev(prev_check, rules->list, list) {
5267 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5268 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5269 tcpcheck->expect.head = prev_check;
5270 continue;
5271 }
5272 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5273 break;
5274 }
5275 LIST_ADDQ(rules->list, &tcpcheck->list);
5276 return 1;
5277}
5278
5279static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5280{
5281 struct tcpcheck_rule *tcpcheck;
5282 struct tcpcheck_send *send;
5283 const char *in;
5284 char *dst;
5285 int i;
5286
5287 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5288 return 0;
5289 memset(tcpcheck, 0, sizeof(*tcpcheck));
5290 tcpcheck->action = TCPCHK_ACT_SEND;
5291
5292 send = &tcpcheck->send;
5293 send->type = TCPCHK_SEND_STRING;
5294
5295 for (i = 0; strs[i]; i++)
5296 send->data.len += strlen(strs[i]);
5297
Christopher Fauletb61caf42020-04-21 10:57:42 +02005298 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005299 if (!isttest(send->data)) {
5300 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5301 return 0;
5302 }
5303
Christopher Fauletb61caf42020-04-21 10:57:42 +02005304 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005305 for (i = 0; strs[i]; i++)
5306 for (in = strs[i]; (*dst = *in++); dst++);
5307 *dst = 0;
5308
5309 LIST_ADDQ(rules->list, &tcpcheck->list);
5310 return 1;
5311}
5312
5313static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5314 struct email_alertq *q, const char *msg)
5315{
5316 struct email_alert *alert;
5317 struct tcpcheck_rule *tcpcheck;
5318 struct check *check = &q->check;
5319
5320 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5321 goto error;
5322 LIST_INIT(&alert->list);
5323 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5324 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5325 if (!alert->rules.list)
5326 goto error;
5327 LIST_INIT(alert->rules.list);
5328 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5329 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005330
Christopher Faulet61cc8522020-04-20 14:54:42 +02005331 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5332 goto error;
5333 memset(tcpcheck, 0, sizeof(*tcpcheck));
5334 tcpcheck->action = TCPCHK_ACT_CONNECT;
5335 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005336
Christopher Faulet61cc8522020-04-20 14:54:42 +02005337 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005338
Christopher Faulet61cc8522020-04-20 14:54:42 +02005339 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005340 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005341
5342 {
5343 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5344 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5345 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005346 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005347
Christopher Faulet61cc8522020-04-20 14:54:42 +02005348 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5349 goto error;
5350
5351 {
5352 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5353 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005354 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005355 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005356
5357 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5358 goto error;
5359
5360 {
5361 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5362 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005363 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005364 }
5365
Christopher Faulet61cc8522020-04-20 14:54:42 +02005366 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5367 goto error;
5368
5369 {
5370 const char * const strs[2] = { "DATA\r\n" };
5371 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005372 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005373 }
5374
5375 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5376 goto error;
5377
5378 {
5379 struct tm tm;
5380 char datestr[48];
5381 const char * const strs[18] = {
5382 "From: ", p->email_alert.from, "\r\n",
5383 "To: ", p->email_alert.to, "\r\n",
5384 "Date: ", datestr, "\r\n",
5385 "Subject: [HAproxy Alert] ", msg, "\r\n",
5386 "\r\n",
5387 msg, "\r\n",
5388 "\r\n",
5389 ".\r\n",
5390 NULL
5391 };
5392
5393 get_localtime(date.tv_sec, &tm);
5394
5395 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005396 goto error;
5397 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005398
5399 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005400 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005401 }
5402
5403 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005404 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005405
5406 {
5407 const char * const strs[2] = { "QUIT\r\n" };
5408 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5409 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005410 }
5411
Christopher Faulet61cc8522020-04-20 14:54:42 +02005412 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5413 goto error;
5414
5415 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5416 task_wakeup(check->task, TASK_WOKEN_MSG);
5417 LIST_ADDQ(&q->email_alerts, &alert->list);
5418 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5419 return 1;
5420
5421error:
5422 email_alert_free(alert);
5423 return 0;
5424}
5425
5426static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5427{
5428 int i;
5429 struct mailer *mailer;
5430
5431 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5432 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5433 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5434 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5435 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005436 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005437 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005438
Christopher Faulet61cc8522020-04-20 14:54:42 +02005439 return;
5440}
5441
5442/*
5443 * Send email alert if configured.
5444 */
5445void send_email_alert(struct server *s, int level, const char *format, ...)
5446{
5447 va_list argp;
5448 char buf[1024];
5449 int len;
5450 struct proxy *p = s->proxy;
5451
5452 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5453 return;
5454
5455 va_start(argp, format);
5456 len = vsnprintf(buf, sizeof(buf), format, argp);
5457 va_end(argp);
5458
5459 if (len < 0 || len >= sizeof(buf)) {
5460 ha_alert("Email alert [%s] could not format message\n", p->id);
5461 return;
5462 }
5463
5464 enqueue_email_alert(p, s, buf);
5465}
5466
5467/**************************************************************************/
5468/************************** Check sample fetches **************************/
5469/**************************************************************************/
5470/* extracts check payload at a fixed position and length */
5471static int
5472smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5473{
5474 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5475 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5476 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5477 struct buffer *buf;
5478
5479 if (!check)
5480 return 0;
5481
5482 buf = &check->bi;
5483 if (buf_offset > b_data(buf))
5484 goto no_match;
5485 if (buf_offset + buf_size > b_data(buf))
5486 buf_size = 0;
5487
5488 /* init chunk as read only */
5489 smp->data.type = SMP_T_STR;
5490 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5491 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5492
5493 return 1;
5494
5495 no_match:
5496 smp->flags = 0;
5497 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005498}
5499
Christopher Faulet61cc8522020-04-20 14:54:42 +02005500static struct sample_fetch_kw_list smp_kws = {ILH, {
5501 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5502 { /* END */ },
5503}};
5504
5505INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5506
5507
5508/**************************************************************************/
5509/************************ Check's parsing functions ***********************/
5510/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005511/* Parses the "tcp-check" proxy keyword */
5512static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5513 struct proxy *defpx, const char *file, int line,
5514 char **errmsg)
5515{
Christopher Faulet404f9192020-04-09 23:13:54 +02005516 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005517 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005518 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005519
5520 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5521 ret = 1;
5522
Christopher Faulet404f9192020-04-09 23:13:54 +02005523 /* Deduce the ruleset name from the proxy info */
5524 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5525 ((curpx == defpx) ? "defaults" : curpx->id),
5526 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005527
Christopher Faulet61cc8522020-04-20 14:54:42 +02005528 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005529 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005530 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005531 if (rs == NULL) {
5532 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005533 goto error;
5534 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005535 }
5536
Gaetan Rivet5301b012020-02-25 17:19:17 +01005537 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005538 if (!LIST_ISEMPTY(&rs->rules)) {
5539 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005540 index = chk->index + 1;
5541 }
5542
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005543 cur_arg = 1;
5544 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005545 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005546 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005547 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005548 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005549 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005550 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005551 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005552 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005553 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5554
5555 if (!kw) {
5556 action_kw_tcp_check_build_list(&trash);
5557 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5558 "%s%s. but got '%s'",
5559 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5560 goto error;
5561 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005562 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005563 }
5564
5565 if (!chk) {
5566 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5567 goto error;
5568 }
Christopher Faulet528f4812020-04-28 10:47:28 +02005569 ret = (ret || (*errmsg != NULL)); /* Handle warning */
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005570
5571 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005572 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005573 LIST_ADDQ(&rs->rules, &chk->list);
5574
5575 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005576 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005577 /* Use this ruleset if the proxy already has tcp-check enabled */
5578 curpx->tcpcheck_rules.list = &rs->rules;
5579 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5580 }
5581 else {
5582 /* mark this ruleset as unused for now */
5583 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5584 }
5585
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005586 return ret;
5587
5588 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005589 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005590 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005591 return -1;
5592}
5593
Christopher Faulet51b129f2020-04-09 15:54:18 +02005594/* Parses the "http-check" proxy keyword */
5595static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5596 struct proxy *defpx, const char *file, int line,
5597 char **errmsg)
5598{
Christopher Faulete5870d82020-04-15 11:32:03 +02005599 struct tcpcheck_ruleset *rs = NULL;
5600 struct tcpcheck_rule *chk = NULL;
5601 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005602
5603 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5604 ret = 1;
5605
5606 cur_arg = 1;
5607 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5608 /* enable a graceful server shutdown on an HTTP 404 response */
5609 curpx->options |= PR_O_DISABLE404;
5610 if (too_many_args(1, args, errmsg, NULL))
5611 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005612 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005613 }
5614 else if (strcmp(args[cur_arg], "send-state") == 0) {
5615 /* enable emission of the apparent state of a server in HTTP checks */
5616 curpx->options2 |= PR_O2_CHK_SNDST;
5617 if (too_many_args(1, args, errmsg, NULL))
5618 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005619 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005620 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005621
Christopher Faulete5870d82020-04-15 11:32:03 +02005622 /* Deduce the ruleset name from the proxy info */
5623 chunk_printf(&trash, "*http-check-%s_%s-%d",
5624 ((curpx == defpx) ? "defaults" : curpx->id),
5625 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005626
Christopher Faulet61cc8522020-04-20 14:54:42 +02005627 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005628 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005629 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005630 if (rs == NULL) {
5631 memprintf(errmsg, "out of memory.\n");
5632 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005633 }
5634 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005635
Christopher Faulete5870d82020-04-15 11:32:03 +02005636 index = 0;
5637 if (!LIST_ISEMPTY(&rs->rules)) {
5638 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5639 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5640 index = chk->index + 1;
5641 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005642
Christopher Faulete5870d82020-04-15 11:32:03 +02005643 if (strcmp(args[cur_arg], "connect") == 0)
5644 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5645 else if (strcmp(args[cur_arg], "send") == 0)
5646 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5647 else if (strcmp(args[cur_arg], "expect") == 0)
5648 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5649 file, line, errmsg);
5650 else if (strcmp(args[cur_arg], "comment") == 0)
5651 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5652 else {
5653 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005654
Christopher Faulete5870d82020-04-15 11:32:03 +02005655 if (!kw) {
5656 action_kw_tcp_check_build_list(&trash);
5657 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5658 " 'send', 'expect'%s%s. but got '%s'",
5659 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5660 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005661 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005662 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5663 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005664
Christopher Faulete5870d82020-04-15 11:32:03 +02005665 if (!chk) {
5666 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5667 goto error;
5668 }
5669 ret = (*errmsg != NULL); /* Handle warning */
5670
5671 chk->index = index;
5672 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5673 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5674 /* Use this ruleset if the proxy already has http-check enabled */
5675 curpx->tcpcheck_rules.list = &rs->rules;
5676 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5677 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5678 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5679 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005680 goto error;
5681 }
5682 }
5683 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005684 /* mark this ruleset as unused for now */
5685 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5686 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005687 }
5688
Christopher Faulete5870d82020-04-15 11:32:03 +02005689 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005690 return ret;
5691
5692 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005693 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005694 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005695 return -1;
5696}
5697
Christopher Faulete9111b62020-04-09 18:12:08 +02005698/* Parses the "external-check" proxy keyword */
5699static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5700 struct proxy *defpx, const char *file, int line,
5701 char **errmsg)
5702{
5703 int cur_arg, ret = 0;
5704
5705 cur_arg = 1;
5706 if (!*(args[cur_arg])) {
5707 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5708 goto error;
5709 }
5710
5711 if (strcmp(args[cur_arg], "command") == 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_command);
5719 curpx->check_command = strdup(args[cur_arg+1]);
5720 }
5721 else if (strcmp(args[cur_arg], "path") == 0) {
5722 if (too_many_args(2, args, errmsg, NULL))
5723 goto error;
5724 if (!*(args[cur_arg+1])) {
5725 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5726 goto error;
5727 }
5728 free(curpx->check_path);
5729 curpx->check_path = strdup(args[cur_arg+1]);
5730 }
5731 else {
5732 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5733 args[0], args[1]);
5734 goto error;
5735 }
5736
5737 ret = (*errmsg != NULL); /* Handle warning */
5738 return ret;
5739
5740error:
5741 return -1;
5742}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005743
Christopher Faulet430e4802020-04-09 15:28:16 +02005744/* Parses the "option tcp-check" proxy keyword */
5745int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5746 const char *file, int line)
5747{
Christopher Faulet404f9192020-04-09 23:13:54 +02005748 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005749 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5750 int err_code = 0;
5751
5752 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5753 err_code |= ERR_WARN;
5754
5755 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5756 goto out;
5757
Christopher Faulet404f9192020-04-09 23:13:54 +02005758 curpx->options2 &= ~PR_O2_CHK_ANY;
5759 curpx->options2 |= PR_O2_TCPCHK_CHK;
5760
Christopher Fauletd7e63962020-04-17 20:15:59 +02005761 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005762 /* If a tcp-check rulesset is already set, do nothing */
5763 if (rules->list)
5764 goto out;
5765
5766 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5767 * get it.
5768 */
5769 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5770 goto curpx_ruleset;
5771
5772 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5773 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005774 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005775 if (rs)
5776 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005777 }
5778
Christopher Faulet404f9192020-04-09 23:13:54 +02005779 curpx_ruleset:
5780 /* Deduce the ruleset name from the proxy info */
5781 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5782 ((curpx == defpx) ? "defaults" : curpx->id),
5783 curpx->conf.file, curpx->conf.line);
5784
Christopher Faulet61cc8522020-04-20 14:54:42 +02005785 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005786 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005787 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005788 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005789 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5790 goto error;
5791 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005792 }
5793
Christopher Faulet404f9192020-04-09 23:13:54 +02005794 ruleset_found:
5795 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005796 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005797 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005798
5799 out:
5800 return err_code;
5801
5802 error:
5803 err_code |= ERR_ALERT | ERR_FATAL;
5804 goto out;
5805}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005806
5807/* Parses the "option redis-check" proxy keyword */
5808int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5809 const char *file, int line)
5810{
5811 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5812 static char *redis_res = "+PONG\r\n";
5813
5814 struct tcpcheck_ruleset *rs = NULL;
5815 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5816 struct tcpcheck_rule *chk;
5817 char *errmsg = NULL;
5818 int err_code = 0;
5819
5820 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5821 err_code |= ERR_WARN;
5822
5823 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5824 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005825
5826 curpx->options2 &= ~PR_O2_CHK_ANY;
5827 curpx->options2 |= PR_O2_TCPCHK_CHK;
5828
5829 free_tcpcheck_vars(&rules->preset_vars);
5830 rules->list = NULL;
5831 rules->flags = 0;
5832
Christopher Faulet61cc8522020-04-20 14:54:42 +02005833 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005834 if (rs)
5835 goto ruleset_found;
5836
Christopher Faulet61cc8522020-04-20 14:54:42 +02005837 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005838 if (rs == NULL) {
5839 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5840 goto error;
5841 }
5842
5843 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5844 1, curpx, &rs->rules, file, line, &errmsg);
5845 if (!chk) {
5846 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5847 goto error;
5848 }
5849 chk->index = 0;
5850 LIST_ADDQ(&rs->rules, &chk->list);
5851
5852 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5853 "error-status", "L7STS",
5854 "on-error", "%[check.payload(),cut_crlf]",
5855 "on-success", "Redis server is ok",
5856 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005857 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005858 if (!chk) {
5859 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5860 goto error;
5861 }
5862 chk->index = 1;
5863 LIST_ADDQ(&rs->rules, &chk->list);
5864
Christopher Fauletd7cee712020-04-21 13:45:00 +02005865 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005866
5867 ruleset_found:
5868 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005869 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005870
5871 out:
5872 free(errmsg);
5873 return err_code;
5874
5875 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005876 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005877 err_code |= ERR_ALERT | ERR_FATAL;
5878 goto out;
5879}
5880
Christopher Faulet811f78c2020-04-01 11:10:27 +02005881
5882/* Parses the "option ssl-hello-chk" proxy keyword */
5883int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5884 const char *file, int line)
5885{
5886 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5887 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5888 *
5889 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5890 */
5891 static char sslv3_client_hello[] = {
5892 "16" /* ContentType : 0x16 = Hanshake */
5893 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5894 "0079" /* ContentLength : 0x79 bytes after this one */
5895 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5896 "000075" /* HandshakeLength : 0x75 bytes after this one */
5897 "0300" /* Hello Version : 0x0300 = v3 */
5898 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5899 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5900 "00" /* Session ID length : empty (no session ID) */
5901 "004E" /* Cipher Suite Length : 78 bytes after this one */
5902 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5903 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5904 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5905 "000D" "000E" "000F" "0010" /* various bit lengths, */
5906 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5907 "0015" "0016" "0017" "0018"
5908 "0019" "001A" "001B" "002F"
5909 "0030" "0031" "0032" "0033"
5910 "0034" "0035" "0036" "0037"
5911 "0038" "0039" "003A"
5912 "01" /* Compression Length : 0x01 = 1 byte for types */
5913 "00" /* Compression Type : 0x00 = NULL compression */
5914 };
5915
5916 struct tcpcheck_ruleset *rs = NULL;
5917 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5918 struct tcpcheck_rule *chk;
5919 char *errmsg = NULL;
5920 int err_code = 0;
5921
5922 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5923 err_code |= ERR_WARN;
5924
5925 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5926 goto out;
5927
Christopher Faulet811f78c2020-04-01 11:10:27 +02005928 curpx->options2 &= ~PR_O2_CHK_ANY;
5929 curpx->options2 |= PR_O2_TCPCHK_CHK;
5930
5931 free_tcpcheck_vars(&rules->preset_vars);
5932 rules->list = NULL;
5933 rules->flags = 0;
5934
Christopher Faulet61cc8522020-04-20 14:54:42 +02005935 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005936 if (rs)
5937 goto ruleset_found;
5938
Christopher Faulet61cc8522020-04-20 14:54:42 +02005939 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005940 if (rs == NULL) {
5941 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5942 goto error;
5943 }
5944
5945 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5946 1, curpx, &rs->rules, file, line, &errmsg);
5947 if (!chk) {
5948 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5949 goto error;
5950 }
5951 chk->index = 0;
5952 LIST_ADDQ(&rs->rules, &chk->list);
5953
5954 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005955 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005956 "error-status", "L6RSP", "tout-status", "L6TOUT",
5957 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005958 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005959 if (!chk) {
5960 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5961 goto error;
5962 }
5963 chk->index = 1;
5964 LIST_ADDQ(&rs->rules, &chk->list);
5965
Christopher Fauletd7cee712020-04-21 13:45:00 +02005966 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005967
5968 ruleset_found:
5969 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005970 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005971
5972 out:
5973 free(errmsg);
5974 return err_code;
5975
5976 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005977 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005978 err_code |= ERR_ALERT | ERR_FATAL;
5979 goto out;
5980}
5981
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005982/* Parses the "option smtpchk" proxy keyword */
5983int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5984 const char *file, int line)
5985{
5986 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5987
5988 struct tcpcheck_ruleset *rs = NULL;
5989 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5990 struct tcpcheck_rule *chk;
5991 struct tcpcheck_var *var = NULL;
5992 char *cmd = NULL, *errmsg = NULL;
5993 int err_code = 0;
5994
5995 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5996 err_code |= ERR_WARN;
5997
5998 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5999 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006000
6001 curpx->options2 &= ~PR_O2_CHK_ANY;
6002 curpx->options2 |= PR_O2_TCPCHK_CHK;
6003
6004 free_tcpcheck_vars(&rules->preset_vars);
6005 rules->list = NULL;
6006 rules->flags = 0;
6007
6008 cur_arg += 2;
6009 if (*args[cur_arg] && *args[cur_arg+1] &&
6010 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6011 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6012 if (cmd)
6013 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6014 }
6015 else {
6016 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6017 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6018 cmd = strdup("HELO localhost");
6019 }
6020
Christopher Fauletb61caf42020-04-21 10:57:42 +02006021 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006022 if (cmd == NULL || var == NULL) {
6023 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6024 goto error;
6025 }
6026 var->data.type = SMP_T_STR;
6027 var->data.u.str.area = cmd;
6028 var->data.u.str.data = strlen(cmd);
6029 LIST_INIT(&var->list);
6030 LIST_ADDQ(&rules->preset_vars, &var->list);
6031 cmd = NULL;
6032 var = NULL;
6033
Christopher Faulet61cc8522020-04-20 14:54:42 +02006034 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006035 if (rs)
6036 goto ruleset_found;
6037
Christopher Faulet61cc8522020-04-20 14:54:42 +02006038 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006039 if (rs == NULL) {
6040 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6041 goto error;
6042 }
6043
6044 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6045 1, curpx, &rs->rules, file, line, &errmsg);
6046 if (!chk) {
6047 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6048 goto error;
6049 }
6050 chk->index = 0;
6051 LIST_ADDQ(&rs->rules, &chk->list);
6052
6053 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6054 "min-recv", "4",
6055 "error-status", "L7RSP",
6056 "on-error", "%[check.payload(),cut_crlf]",
6057 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006058 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006059 if (!chk) {
6060 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6061 goto error;
6062 }
6063 chk->index = 1;
6064 LIST_ADDQ(&rs->rules, &chk->list);
6065
6066 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6067 "min-recv", "4",
6068 "error-status", "L7STS",
6069 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6070 "status-code", "check.payload(0,3)",
6071 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006072 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006073 if (!chk) {
6074 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6075 goto error;
6076 }
6077 chk->index = 2;
6078 LIST_ADDQ(&rs->rules, &chk->list);
6079
6080 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6081 1, curpx, &rs->rules, file, line, &errmsg);
6082 if (!chk) {
6083 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6084 goto error;
6085 }
6086 chk->index = 3;
6087 LIST_ADDQ(&rs->rules, &chk->list);
6088
6089 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6090 "min-recv", "4",
6091 "error-status", "L7STS",
6092 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6093 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6094 "status-code", "check.payload(0,3)",
6095 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006096 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006097 if (!chk) {
6098 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6099 goto error;
6100 }
6101 chk->index = 4;
6102 LIST_ADDQ(&rs->rules, &chk->list);
6103
Christopher Fauletd7cee712020-04-21 13:45:00 +02006104 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006105
6106 ruleset_found:
6107 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006108 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006109
6110 out:
6111 free(errmsg);
6112 return err_code;
6113
6114 error:
6115 free(cmd);
6116 free(var);
6117 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006118 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006119 err_code |= ERR_ALERT | ERR_FATAL;
6120 goto out;
6121}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006122
Christopher Fauletce355072020-04-02 11:44:39 +02006123/* Parses the "option pgsql-check" proxy keyword */
6124int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6125 const char *file, int line)
6126{
6127 static char pgsql_req[] = {
6128 "%[var(check.plen),htonl,hex]" /* The packet length*/
6129 "00030000" /* the version 3.0 */
6130 "7573657200" /* "user" key */
6131 "%[var(check.username),hex]00" /* the username */
6132 "00"
6133 };
6134
6135 struct tcpcheck_ruleset *rs = NULL;
6136 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6137 struct tcpcheck_rule *chk;
6138 struct tcpcheck_var *var = NULL;
6139 char *user = NULL, *errmsg = NULL;
6140 size_t packetlen = 0;
6141 int err_code = 0;
6142
6143 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6144 err_code |= ERR_WARN;
6145
6146 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6147 goto out;
6148
Christopher Fauletce355072020-04-02 11:44:39 +02006149 curpx->options2 &= ~PR_O2_CHK_ANY;
6150 curpx->options2 |= PR_O2_TCPCHK_CHK;
6151
6152 free_tcpcheck_vars(&rules->preset_vars);
6153 rules->list = NULL;
6154 rules->flags = 0;
6155
6156 cur_arg += 2;
6157 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6158 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6159 file, line, args[0], args[1]);
6160 goto error;
6161 }
6162 if (strcmp(args[cur_arg], "user") == 0) {
6163 packetlen = 15 + strlen(args[cur_arg+1]);
6164 user = strdup(args[cur_arg+1]);
6165
Christopher Fauletb61caf42020-04-21 10:57:42 +02006166 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006167 if (user == NULL || var == NULL) {
6168 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6169 goto error;
6170 }
6171 var->data.type = SMP_T_STR;
6172 var->data.u.str.area = user;
6173 var->data.u.str.data = strlen(user);
6174 LIST_INIT(&var->list);
6175 LIST_ADDQ(&rules->preset_vars, &var->list);
6176 user = NULL;
6177 var = NULL;
6178
Christopher Fauletb61caf42020-04-21 10:57:42 +02006179 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006180 if (var == NULL) {
6181 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6182 goto error;
6183 }
6184 var->data.type = SMP_T_SINT;
6185 var->data.u.sint = packetlen;
6186 LIST_INIT(&var->list);
6187 LIST_ADDQ(&rules->preset_vars, &var->list);
6188 var = NULL;
6189 }
6190 else {
6191 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6192 file, line, args[0], args[1]);
6193 goto error;
6194 }
6195
Christopher Faulet61cc8522020-04-20 14:54:42 +02006196 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006197 if (rs)
6198 goto ruleset_found;
6199
Christopher Faulet61cc8522020-04-20 14:54:42 +02006200 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006201 if (rs == NULL) {
6202 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6203 goto error;
6204 }
6205
6206 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6207 1, curpx, &rs->rules, file, line, &errmsg);
6208 if (!chk) {
6209 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6210 goto error;
6211 }
6212 chk->index = 0;
6213 LIST_ADDQ(&rs->rules, &chk->list);
6214
6215 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6216 1, curpx, &rs->rules, file, line, &errmsg);
6217 if (!chk) {
6218 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6219 goto error;
6220 }
6221 chk->index = 1;
6222 LIST_ADDQ(&rs->rules, &chk->list);
6223
6224 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6225 "min-recv", "5",
6226 "error-status", "L7RSP",
6227 "on-error", "%[check.payload(6,0)]",
6228 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006229 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006230 if (!chk) {
6231 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6232 goto error;
6233 }
6234 chk->index = 2;
6235 LIST_ADDQ(&rs->rules, &chk->list);
6236
Christopher Fauletb841c742020-04-27 18:29:49 +02006237 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 +02006238 "min-recv", "9",
6239 "error-status", "L7STS",
6240 "on-success", "PostgreSQL server is ok",
6241 "on-error", "PostgreSQL unknown error",
6242 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006243 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006244 if (!chk) {
6245 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6246 goto error;
6247 }
6248 chk->index = 3;
6249 LIST_ADDQ(&rs->rules, &chk->list);
6250
Christopher Fauletd7cee712020-04-21 13:45:00 +02006251 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006252
6253 ruleset_found:
6254 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006255 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006256
6257 out:
6258 free(errmsg);
6259 return err_code;
6260
6261 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006262 free(user);
6263 free(var);
6264 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006265 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006266 err_code |= ERR_ALERT | ERR_FATAL;
6267 goto out;
6268}
6269
6270
6271/* Parses the "option mysql-check" proxy keyword */
6272int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6273 const char *file, int line)
6274{
6275 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6276 * const char mysql40_client_auth_pkt[] = {
6277 * "\x0e\x00\x00" // packet length
6278 * "\x01" // packet number
6279 * "\x00\x00" // client capabilities
6280 * "\x00\x00\x01" // max packet
6281 * "haproxy\x00" // username (null terminated string)
6282 * "\x00" // filler (always 0x00)
6283 * "\x01\x00\x00" // packet length
6284 * "\x00" // packet number
6285 * "\x01" // COM_QUIT command
6286 * };
6287 */
6288 static char mysql40_rsname[] = "*mysql40-check";
6289 static char mysql40_req[] = {
6290 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6291 "0080" /* client capabilities */
6292 "000001" /* max packet */
6293 "%[var(check.username),hex]00" /* the username */
6294 "00" /* filler (always 0x00) */
6295 "010000" /* packet length*/
6296 "00" /* sequence ID */
6297 "01" /* COM_QUIT command */
6298 };
6299
6300 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6301 * const char mysql41_client_auth_pkt[] = {
6302 * "\x0e\x00\x00\" // packet length
6303 * "\x01" // packet number
6304 * "\x00\x00\x00\x00" // client capabilities
6305 * "\x00\x00\x00\x01" // max packet
6306 * "\x21" // character set (UTF-8)
6307 * char[23] // All zeroes
6308 * "haproxy\x00" // username (null terminated string)
6309 * "\x00" // filler (always 0x00)
6310 * "\x01\x00\x00" // packet length
6311 * "\x00" // packet number
6312 * "\x01" // COM_QUIT command
6313 * };
6314 */
6315 static char mysql41_rsname[] = "*mysql41-check";
6316 static char mysql41_req[] = {
6317 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6318 "00820000" /* client capabilities */
6319 "00800001" /* max packet */
6320 "21" /* character set (UTF-8) */
6321 "000000000000000000000000" /* 23 bytes, al zeroes */
6322 "0000000000000000000000"
6323 "%[var(check.username),hex]00" /* the username */
6324 "00" /* filler (always 0x00) */
6325 "010000" /* packet length*/
6326 "00" /* sequence ID */
6327 "01" /* COM_QUIT command */
6328 };
6329
6330 struct tcpcheck_ruleset *rs = NULL;
6331 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6332 struct tcpcheck_rule *chk;
6333 struct tcpcheck_var *var = NULL;
6334 char *mysql_rsname = "*mysql-check";
6335 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6336 int index = 0, err_code = 0;
6337
6338 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6339 err_code |= ERR_WARN;
6340
6341 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6342 goto out;
6343
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006344 curpx->options2 &= ~PR_O2_CHK_ANY;
6345 curpx->options2 |= PR_O2_TCPCHK_CHK;
6346
6347 free_tcpcheck_vars(&rules->preset_vars);
6348 rules->list = NULL;
6349 rules->flags = 0;
6350
6351 cur_arg += 2;
6352 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006353 int packetlen, userlen;
6354
6355 if (strcmp(args[cur_arg], "user") != 0) {
6356 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6357 file, line, args[0], args[1], args[cur_arg]);
6358 goto error;
6359 }
6360
6361 if (*(args[cur_arg+1]) == 0) {
6362 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6363 file, line, args[0], args[1], args[cur_arg]);
6364 goto error;
6365 }
6366
6367 hdr = calloc(4, sizeof(*hdr));
6368 user = strdup(args[cur_arg+1]);
6369 userlen = strlen(args[cur_arg+1]);
6370
6371 if (hdr == NULL || user == NULL) {
6372 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6373 goto error;
6374 }
6375
6376 if (*args[cur_arg+2]) {
6377 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6378 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6379 file, line, args[cur_arg], args[cur_arg+2]);
6380 goto error;
6381 }
6382 packetlen = userlen + 7 + 27;
6383 mysql_req = mysql41_req;
6384 mysql_rsname = mysql41_rsname;
6385 }
6386 else {
6387 packetlen = userlen + 7;
6388 mysql_req = mysql40_req;
6389 mysql_rsname = mysql40_rsname;
6390 }
6391
6392 hdr[0] = (unsigned char)(packetlen & 0xff);
6393 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6394 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6395 hdr[3] = 1;
6396
Christopher Fauletb61caf42020-04-21 10:57:42 +02006397 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006398 if (var == NULL) {
6399 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6400 goto error;
6401 }
6402 var->data.type = SMP_T_STR;
6403 var->data.u.str.area = hdr;
6404 var->data.u.str.data = 4;
6405 LIST_INIT(&var->list);
6406 LIST_ADDQ(&rules->preset_vars, &var->list);
6407 hdr = NULL;
6408 var = NULL;
6409
Christopher Fauletb61caf42020-04-21 10:57:42 +02006410 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006411 if (var == NULL) {
6412 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6413 goto error;
6414 }
6415 var->data.type = SMP_T_STR;
6416 var->data.u.str.area = user;
6417 var->data.u.str.data = strlen(user);
6418 LIST_INIT(&var->list);
6419 LIST_ADDQ(&rules->preset_vars, &var->list);
6420 user = NULL;
6421 var = NULL;
6422 }
6423
Christopher Faulet61cc8522020-04-20 14:54:42 +02006424 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006425 if (rs)
6426 goto ruleset_found;
6427
Christopher Faulet61cc8522020-04-20 14:54:42 +02006428 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006429 if (rs == NULL) {
6430 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6431 goto error;
6432 }
6433
6434 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6435 1, curpx, &rs->rules, file, line, &errmsg);
6436 if (!chk) {
6437 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6438 goto error;
6439 }
6440 chk->index = index++;
6441 LIST_ADDQ(&rs->rules, &chk->list);
6442
6443 if (mysql_req) {
6444 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6445 1, curpx, &rs->rules, file, line, &errmsg);
6446 if (!chk) {
6447 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6448 goto error;
6449 }
6450 chk->index = index++;
6451 LIST_ADDQ(&rs->rules, &chk->list);
6452 }
6453
6454 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006455 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006456 if (!chk) {
6457 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6458 goto error;
6459 }
6460 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6461 chk->index = index++;
6462 LIST_ADDQ(&rs->rules, &chk->list);
6463
6464 if (mysql_req) {
6465 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006466 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006467 if (!chk) {
6468 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6469 goto error;
6470 }
6471 chk->expect.custom = tcpcheck_mysql_expect_ok;
6472 chk->index = index++;
6473 LIST_ADDQ(&rs->rules, &chk->list);
6474 }
6475
Christopher Fauletd7cee712020-04-21 13:45:00 +02006476 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006477
6478 ruleset_found:
6479 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006480 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006481
6482 out:
6483 free(errmsg);
6484 return err_code;
6485
6486 error:
6487 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006488 free(user);
6489 free(var);
6490 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006491 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006492 err_code |= ERR_ALERT | ERR_FATAL;
6493 goto out;
6494}
6495
Christopher Faulet1997eca2020-04-03 23:13:50 +02006496int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6497 const char *file, int line)
6498{
6499 static char *ldap_req = "300C020101600702010304008000";
6500
6501 struct tcpcheck_ruleset *rs = NULL;
6502 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6503 struct tcpcheck_rule *chk;
6504 char *errmsg = NULL;
6505 int err_code = 0;
6506
6507 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6508 err_code |= ERR_WARN;
6509
6510 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6511 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006512
6513 curpx->options2 &= ~PR_O2_CHK_ANY;
6514 curpx->options2 |= PR_O2_TCPCHK_CHK;
6515
6516 free_tcpcheck_vars(&rules->preset_vars);
6517 rules->list = NULL;
6518 rules->flags = 0;
6519
Christopher Faulet61cc8522020-04-20 14:54:42 +02006520 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006521 if (rs)
6522 goto ruleset_found;
6523
Christopher Faulet61cc8522020-04-20 14:54:42 +02006524 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006525 if (rs == NULL) {
6526 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6527 goto error;
6528 }
6529
6530 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6531 1, curpx, &rs->rules, file, line, &errmsg);
6532 if (!chk) {
6533 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6534 goto error;
6535 }
6536 chk->index = 0;
6537 LIST_ADDQ(&rs->rules, &chk->list);
6538
6539 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6540 "min-recv", "14",
6541 "on-error", "Not LDAPv3 protocol",
6542 ""},
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->index = 1;
6549 LIST_ADDQ(&rs->rules, &chk->list);
6550
6551 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006552 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006553 if (!chk) {
6554 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6555 goto error;
6556 }
6557 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6558 chk->index = 2;
6559 LIST_ADDQ(&rs->rules, &chk->list);
6560
Christopher Fauletd7cee712020-04-21 13:45:00 +02006561 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006562
6563 ruleset_found:
6564 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006565 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006566
6567 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006568 free(errmsg);
6569 return err_code;
6570
6571 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006572 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006573 err_code |= ERR_ALERT | ERR_FATAL;
6574 goto out;
6575}
6576
6577int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6578 const char *file, int line)
6579{
6580 struct tcpcheck_ruleset *rs = NULL;
6581 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6582 struct tcpcheck_rule *chk;
6583 char *spop_req = NULL;
6584 char *errmsg = NULL;
6585 int spop_len = 0, err_code = 0;
6586
6587 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6588 err_code |= ERR_WARN;
6589
6590 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6591 goto out;
6592
Christopher Faulet267b01b2020-04-04 10:27:09 +02006593 curpx->options2 &= ~PR_O2_CHK_ANY;
6594 curpx->options2 |= PR_O2_TCPCHK_CHK;
6595
6596 free_tcpcheck_vars(&rules->preset_vars);
6597 rules->list = NULL;
6598 rules->flags = 0;
6599
6600
Christopher Faulet61cc8522020-04-20 14:54:42 +02006601 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006602 if (rs)
6603 goto ruleset_found;
6604
Christopher Faulet61cc8522020-04-20 14:54:42 +02006605 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006606 if (rs == NULL) {
6607 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6608 goto error;
6609 }
6610
6611 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6612 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6613 goto error;
6614 }
6615 chunk_reset(&trash);
6616 dump_binary(&trash, spop_req, spop_len);
6617 trash.area[trash.data] = '\0';
6618
6619 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6620 1, curpx, &rs->rules, file, line, &errmsg);
6621 if (!chk) {
6622 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6623 goto error;
6624 }
6625 chk->index = 0;
6626 LIST_ADDQ(&rs->rules, &chk->list);
6627
6628 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006629 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006630 if (!chk) {
6631 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6632 goto error;
6633 }
6634 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6635 chk->index = 1;
6636 LIST_ADDQ(&rs->rules, &chk->list);
6637
Christopher Fauletd7cee712020-04-21 13:45:00 +02006638 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006639
6640 ruleset_found:
6641 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006642 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006643
6644 out:
6645 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006646 free(errmsg);
6647 return err_code;
6648
6649 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006650 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006651 err_code |= ERR_ALERT | ERR_FATAL;
6652 goto out;
6653}
Christopher Fauletce355072020-04-02 11:44:39 +02006654
Christopher Faulete5870d82020-04-15 11:32:03 +02006655
6656struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6657{
6658 struct tcpcheck_rule *chk = NULL;
6659 struct tcpcheck_http_hdr *hdr = NULL;
6660 char *meth = NULL, *uri = NULL, *vsn = NULL;
6661 char *hdrs, *body;
6662
6663 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6664 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6665 if (hdrs == body)
6666 hdrs = NULL;
6667 if (hdrs) {
6668 *hdrs = '\0';
6669 hdrs +=2;
6670 }
6671 if (body) {
6672 *body = '\0';
6673 body += 4;
6674 }
6675 if (hdrs || body) {
6676 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6677 " Please, consider to use 'http-check send' directive instead.");
6678 }
6679
6680 chk = calloc(1, sizeof(*chk));
6681 if (!chk) {
6682 memprintf(errmsg, "out of memory");
6683 goto error;
6684 }
6685 chk->action = TCPCHK_ACT_SEND;
6686 chk->send.type = TCPCHK_SEND_HTTP;
6687 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6688 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6689 LIST_INIT(&chk->send.http.hdrs);
6690
6691 /* Copy the method, uri and version */
6692 if (*args[cur_arg]) {
6693 if (!*args[cur_arg+1])
6694 uri = args[cur_arg];
6695 else
6696 meth = args[cur_arg];
6697 }
6698 if (*args[cur_arg+1])
6699 uri = args[cur_arg+1];
6700 if (*args[cur_arg+2])
6701 vsn = args[cur_arg+2];
6702
6703 if (meth) {
6704 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6705 chk->send.http.meth.str.area = strdup(meth);
6706 chk->send.http.meth.str.data = strlen(meth);
6707 if (!chk->send.http.meth.str.area) {
6708 memprintf(errmsg, "out of memory");
6709 goto error;
6710 }
6711 }
6712 if (uri) {
6713 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006714 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006715 memprintf(errmsg, "out of memory");
6716 goto error;
6717 }
6718 }
6719 if (vsn) {
6720 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006721 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006722 memprintf(errmsg, "out of memory");
6723 goto error;
6724 }
6725 }
6726
6727 /* Copy the header */
6728 if (hdrs) {
6729 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6730 struct h1m h1m;
6731 int i, ret;
6732
6733 /* Build and parse the request */
6734 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6735
6736 h1m.flags = H1_MF_HDRS_ONLY;
6737 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6738 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6739 &h1m, NULL);
6740 if (ret <= 0) {
6741 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6742 goto error;
6743 }
6744
Christopher Fauletb61caf42020-04-21 10:57:42 +02006745 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006746 hdr = calloc(1, sizeof(*hdr));
6747 if (!hdr) {
6748 memprintf(errmsg, "out of memory");
6749 goto error;
6750 }
6751 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006752 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006753 if (!hdr->name.ptr) {
6754 memprintf(errmsg, "out of memory");
6755 goto error;
6756 }
6757
Christopher Fauletb61caf42020-04-21 10:57:42 +02006758 ist0(tmp_hdrs[i].v);
6759 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 +02006760 goto error;
6761 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6762 }
6763 }
6764
6765 /* Copy the body */
6766 if (body) {
6767 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006768 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006769 memprintf(errmsg, "out of memory");
6770 goto error;
6771 }
6772 }
6773
6774 return chk;
6775
6776 error:
6777 free_tcpcheck_http_hdr(hdr);
6778 free_tcpcheck(chk, 0);
6779 return NULL;
6780}
6781
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006782int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6783 const char *file, int line)
6784{
Christopher Faulete5870d82020-04-15 11:32:03 +02006785 struct tcpcheck_ruleset *rs = NULL;
6786 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6787 struct tcpcheck_rule *chk;
6788 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006789 int err_code = 0;
6790
6791 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6792 err_code |= ERR_WARN;
6793
6794 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6795 goto out;
6796
Christopher Faulete5870d82020-04-15 11:32:03 +02006797 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6798 if (!chk) {
6799 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6800 goto error;
6801 }
6802 if (errmsg) {
6803 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6804 err_code |= ERR_WARN;
6805 free(errmsg);
6806 errmsg = NULL;
6807 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006808
Christopher Faulete5870d82020-04-15 11:32:03 +02006809 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006810 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006811 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006812
Christopher Faulete5870d82020-04-15 11:32:03 +02006813 free_tcpcheck_vars(&rules->preset_vars);
6814 rules->list = NULL;
6815 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006816
Christopher Faulete5870d82020-04-15 11:32:03 +02006817 /* Deduce the ruleset name from the proxy info */
6818 chunk_printf(&trash, "*http-check-%s_%s-%d",
6819 ((curpx == defpx) ? "defaults" : curpx->id),
6820 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006821
Christopher Faulet61cc8522020-04-20 14:54:42 +02006822 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006823 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006824 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006825 if (rs == NULL) {
6826 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
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
Christopher Faulete5870d82020-04-15 11:32:03 +02006831 rules->list = &rs->rules;
6832 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6833 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6834 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6835 rules->list = NULL;
6836 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006837 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006838
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006839 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006840 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006841 return err_code;
6842
6843 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006844 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006845 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006846 err_code |= ERR_ALERT | ERR_FATAL;
6847 goto out;
6848}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006849
Christopher Faulet6f557912020-04-09 15:58:50 +02006850int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6851 const char *file, int line)
6852{
6853 int err_code = 0;
6854
Christopher Faulet6f557912020-04-09 15:58:50 +02006855 curpx->options2 &= ~PR_O2_CHK_ANY;
6856 curpx->options2 |= PR_O2_EXT_CHK;
6857 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6858 goto out;
6859
6860 out:
6861 return err_code;
6862}
6863
Christopher Fauletce8111e2020-04-06 15:04:11 +02006864/* Parse the "addr" server keyword */
6865static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6866 char **errmsg)
6867{
6868 struct sockaddr_storage *sk;
6869 struct protocol *proto;
6870 int port1, port2, err_code = 0;
6871
6872
6873 if (!*args[*cur_arg+1]) {
6874 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6875 goto error;
6876 }
6877
6878 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6879 if (!sk) {
6880 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6881 goto error;
6882 }
6883
6884 proto = protocol_by_family(sk->ss_family);
6885 if (!proto || !proto->connect) {
6886 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6887 args[*cur_arg], args[*cur_arg+1]);
6888 goto error;
6889 }
6890
6891 if (port1 != port2) {
6892 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6893 args[*cur_arg], args[*cur_arg+1]);
6894 goto error;
6895 }
6896
6897 srv->check.addr = srv->agent.addr = *sk;
6898 srv->flags |= SRV_F_CHECKADDR;
6899 srv->flags |= SRV_F_AGENTADDR;
6900
6901 out:
6902 return err_code;
6903
6904 error:
6905 err_code |= ERR_ALERT | ERR_FATAL;
6906 goto out;
6907}
6908
6909
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006910/* Parse the "agent-addr" server keyword */
6911static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6912 char **errmsg)
6913{
6914 int err_code = 0;
6915
6916 if (!*(args[*cur_arg+1])) {
6917 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6918 goto error;
6919 }
6920 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6921 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6922 goto error;
6923 }
6924
6925 out:
6926 return err_code;
6927
6928 error:
6929 err_code |= ERR_ALERT | ERR_FATAL;
6930 goto out;
6931}
6932
6933/* Parse the "agent-check" server keyword */
6934static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6935 char **errmsg)
6936{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006937 struct tcpcheck_ruleset *rs = NULL;
6938 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6939 struct tcpcheck_rule *chk;
6940 int err_code = 0;
6941
6942 if (srv->do_agent)
6943 goto out;
6944
6945 if (!rules) {
6946 rules = calloc(1, sizeof(*rules));
6947 if (!rules) {
6948 memprintf(errmsg, "out of memory.");
6949 goto error;
6950 }
6951 LIST_INIT(&rules->preset_vars);
6952 srv->agent.tcpcheck_rules = rules;
6953 }
6954 rules->list = NULL;
6955 rules->flags = 0;
6956
Christopher Faulet61cc8522020-04-20 14:54:42 +02006957 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006958 if (rs)
6959 goto ruleset_found;
6960
Christopher Faulet61cc8522020-04-20 14:54:42 +02006961 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006962 if (rs == NULL) {
6963 memprintf(errmsg, "out of memory.");
6964 goto error;
6965 }
6966
6967 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6968 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6969 if (!chk) {
6970 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6971 goto error;
6972 }
6973 chk->index = 0;
6974 LIST_ADDQ(&rs->rules, &chk->list);
6975
6976 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006977 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6978 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006979 if (!chk) {
6980 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6981 goto error;
6982 }
6983 chk->expect.custom = tcpcheck_agent_expect_reply;
6984 chk->index = 1;
6985 LIST_ADDQ(&rs->rules, &chk->list);
6986
Christopher Fauletd7cee712020-04-21 13:45:00 +02006987 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006988
6989 ruleset_found:
6990 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006991 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006992 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006993
6994 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006995 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006996
6997 error:
6998 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006999 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007000 err_code |= ERR_ALERT | ERR_FATAL;
7001 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007002}
7003
7004/* Parse the "agent-inter" server keyword */
7005static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7006 char **errmsg)
7007{
7008 const char *err = NULL;
7009 unsigned int delay;
7010 int err_code = 0;
7011
7012 if (!*(args[*cur_arg+1])) {
7013 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7014 goto error;
7015 }
7016
7017 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7018 if (err == PARSE_TIME_OVER) {
7019 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7020 args[*cur_arg+1], args[*cur_arg], srv->id);
7021 goto error;
7022 }
7023 else if (err == PARSE_TIME_UNDER) {
7024 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7025 args[*cur_arg+1], args[*cur_arg], srv->id);
7026 goto error;
7027 }
7028 else if (err) {
7029 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7030 *err, srv->id);
7031 goto error;
7032 }
7033 if (delay <= 0) {
7034 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7035 delay, args[*cur_arg], srv->id);
7036 goto error;
7037 }
7038 srv->agent.inter = delay;
7039
7040 out:
7041 return err_code;
7042
7043 error:
7044 err_code |= ERR_ALERT | ERR_FATAL;
7045 goto out;
7046}
7047
7048/* Parse the "agent-port" server keyword */
7049static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7050 char **errmsg)
7051{
7052 int err_code = 0;
7053
7054 if (!*(args[*cur_arg+1])) {
7055 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7056 goto error;
7057 }
7058
7059 global.maxsock++;
7060 srv->agent.port = atol(args[*cur_arg+1]);
7061
7062 out:
7063 return err_code;
7064
7065 error:
7066 err_code |= ERR_ALERT | ERR_FATAL;
7067 goto out;
7068}
7069
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007070int set_srv_agent_send(struct server *srv, const char *send)
7071{
7072 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7073 struct tcpcheck_var *var = NULL;
7074 char *str;
7075
7076 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007077 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007078 if (str == NULL || var == NULL)
7079 goto error;
7080
7081 free_tcpcheck_vars(&rules->preset_vars);
7082
7083 var->data.type = SMP_T_STR;
7084 var->data.u.str.area = str;
7085 var->data.u.str.data = strlen(str);
7086 LIST_INIT(&var->list);
7087 LIST_ADDQ(&rules->preset_vars, &var->list);
7088
7089 return 1;
7090
7091 error:
7092 free(str);
7093 free(var);
7094 return 0;
7095}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007096
7097/* Parse the "agent-send" server keyword */
7098static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7099 char **errmsg)
7100{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007101 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007102 int err_code = 0;
7103
7104 if (!*(args[*cur_arg+1])) {
7105 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7106 goto error;
7107 }
7108
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007109 if (!rules) {
7110 rules = calloc(1, sizeof(*rules));
7111 if (!rules) {
7112 memprintf(errmsg, "out of memory.");
7113 goto error;
7114 }
7115 LIST_INIT(&rules->preset_vars);
7116 srv->agent.tcpcheck_rules = rules;
7117 }
7118
7119 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007120 memprintf(errmsg, "out of memory.");
7121 goto error;
7122 }
7123
7124 out:
7125 return err_code;
7126
7127 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007128 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007129 err_code |= ERR_ALERT | ERR_FATAL;
7130 goto out;
7131}
7132
7133/* Parse the "no-agent-send" server keyword */
7134static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7135 char **errmsg)
7136{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007137 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007138 return 0;
7139}
7140
Christopher Fauletce8111e2020-04-06 15:04:11 +02007141/* Parse the "check" server keyword */
7142static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7143 char **errmsg)
7144{
7145 srv->do_check = 1;
7146 return 0;
7147}
7148
7149/* Parse the "check-send-proxy" server keyword */
7150static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7151 char **errmsg)
7152{
7153 srv->check.send_proxy = 1;
7154 return 0;
7155}
7156
7157/* Parse the "check-via-socks4" server keyword */
7158static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7159 char **errmsg)
7160{
7161 srv->check.via_socks4 = 1;
7162 return 0;
7163}
7164
7165/* Parse the "no-check" server keyword */
7166static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7167 char **errmsg)
7168{
7169 deinit_srv_check(srv);
7170 return 0;
7171}
7172
7173/* Parse the "no-check-send-proxy" server keyword */
7174static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7175 char **errmsg)
7176{
7177 srv->check.send_proxy = 0;
7178 return 0;
7179}
7180
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007181/* parse the "check-proto" server keyword */
7182static int srv_parse_check_proto(char **args, int *cur_arg,
7183 struct proxy *px, struct server *newsrv, char **err)
7184{
7185 int err_code = 0;
7186
7187 if (!*args[*cur_arg + 1]) {
7188 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7189 goto error;
7190 }
7191 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7192 if (!newsrv->check.mux_proto) {
7193 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7194 goto error;
7195 }
7196
7197 out:
7198 return err_code;
7199
7200 error:
7201 err_code |= ERR_ALERT | ERR_FATAL;
7202 goto out;
7203}
7204
7205
Christopher Fauletce8111e2020-04-06 15:04:11 +02007206/* Parse the "rise" server keyword */
7207static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7208 char **errmsg)
7209{
7210 int err_code = 0;
7211
7212 if (!*args[*cur_arg + 1]) {
7213 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7214 goto error;
7215 }
7216
7217 srv->check.rise = atol(args[*cur_arg+1]);
7218 if (srv->check.rise <= 0) {
7219 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7220 goto error;
7221 }
7222
7223 if (srv->check.health)
7224 srv->check.health = srv->check.rise;
7225
7226 out:
7227 return err_code;
7228
7229 error:
7230 deinit_srv_agent_check(srv);
7231 err_code |= ERR_ALERT | ERR_FATAL;
7232 goto out;
7233 return 0;
7234}
7235
7236/* Parse the "fall" server keyword */
7237static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7238 char **errmsg)
7239{
7240 int err_code = 0;
7241
7242 if (!*args[*cur_arg + 1]) {
7243 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7244 goto error;
7245 }
7246
7247 srv->check.fall = atol(args[*cur_arg+1]);
7248 if (srv->check.fall <= 0) {
7249 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7250 goto error;
7251 }
7252
7253 out:
7254 return err_code;
7255
7256 error:
7257 deinit_srv_agent_check(srv);
7258 err_code |= ERR_ALERT | ERR_FATAL;
7259 goto out;
7260 return 0;
7261}
7262
7263/* Parse the "inter" server keyword */
7264static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7265 char **errmsg)
7266{
7267 const char *err = NULL;
7268 unsigned int delay;
7269 int err_code = 0;
7270
7271 if (!*(args[*cur_arg+1])) {
7272 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7273 goto error;
7274 }
7275
7276 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7277 if (err == PARSE_TIME_OVER) {
7278 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7279 args[*cur_arg+1], args[*cur_arg], srv->id);
7280 goto error;
7281 }
7282 else if (err == PARSE_TIME_UNDER) {
7283 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7284 args[*cur_arg+1], args[*cur_arg], srv->id);
7285 goto error;
7286 }
7287 else if (err) {
7288 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7289 *err, srv->id);
7290 goto error;
7291 }
7292 if (delay <= 0) {
7293 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7294 delay, args[*cur_arg], srv->id);
7295 goto error;
7296 }
7297 srv->check.inter = delay;
7298
7299 out:
7300 return err_code;
7301
7302 error:
7303 err_code |= ERR_ALERT | ERR_FATAL;
7304 goto out;
7305}
7306
7307
7308/* Parse the "fastinter" server keyword */
7309static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7310 char **errmsg)
7311{
7312 const char *err = NULL;
7313 unsigned int delay;
7314 int err_code = 0;
7315
7316 if (!*(args[*cur_arg+1])) {
7317 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7318 goto error;
7319 }
7320
7321 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7322 if (err == PARSE_TIME_OVER) {
7323 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7324 args[*cur_arg+1], args[*cur_arg], srv->id);
7325 goto error;
7326 }
7327 else if (err == PARSE_TIME_UNDER) {
7328 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7329 args[*cur_arg+1], args[*cur_arg], srv->id);
7330 goto error;
7331 }
7332 else if (err) {
7333 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7334 *err, srv->id);
7335 goto error;
7336 }
7337 if (delay <= 0) {
7338 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7339 delay, args[*cur_arg], srv->id);
7340 goto error;
7341 }
7342 srv->check.fastinter = delay;
7343
7344 out:
7345 return err_code;
7346
7347 error:
7348 err_code |= ERR_ALERT | ERR_FATAL;
7349 goto out;
7350}
7351
7352
7353/* Parse the "downinter" server keyword */
7354static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7355 char **errmsg)
7356{
7357 const char *err = NULL;
7358 unsigned int delay;
7359 int err_code = 0;
7360
7361 if (!*(args[*cur_arg+1])) {
7362 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7363 goto error;
7364 }
7365
7366 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7367 if (err == PARSE_TIME_OVER) {
7368 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7369 args[*cur_arg+1], args[*cur_arg], srv->id);
7370 goto error;
7371 }
7372 else if (err == PARSE_TIME_UNDER) {
7373 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7374 args[*cur_arg+1], args[*cur_arg], srv->id);
7375 goto error;
7376 }
7377 else if (err) {
7378 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7379 *err, srv->id);
7380 goto error;
7381 }
7382 if (delay <= 0) {
7383 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7384 delay, args[*cur_arg], srv->id);
7385 goto error;
7386 }
7387 srv->check.downinter = delay;
7388
7389 out:
7390 return err_code;
7391
7392 error:
7393 err_code |= ERR_ALERT | ERR_FATAL;
7394 goto out;
7395}
7396
7397/* Parse the "port" server keyword */
7398static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7399 char **errmsg)
7400{
7401 int err_code = 0;
7402
7403 if (!*(args[*cur_arg+1])) {
7404 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7405 goto error;
7406 }
7407
7408 global.maxsock++;
7409 srv->check.port = atol(args[*cur_arg+1]);
7410 srv->flags |= SRV_F_CHECKPORT;
7411
7412 out:
7413 return err_code;
7414
7415 error:
7416 err_code |= ERR_ALERT | ERR_FATAL;
7417 goto out;
7418}
7419
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007420static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007421 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7422 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7423 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007424 { 0, NULL, NULL },
7425}};
7426
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007427static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007428 { "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 +02007429 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7430 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7431 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7432 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7433 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007434 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007435 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007436 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7437 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007438 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007439 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7440 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7441 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7442 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7443 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7444 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7445 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7446 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007447 { NULL, NULL, 0 },
7448}};
7449
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007450INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007451INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007452
Willy Tarreaubd741542010-03-16 18:46:54 +01007453/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007454 * Local variables:
7455 * c-indent-level: 8
7456 * c-basic-offset: 8
7457 * End:
7458 */