blob: 901deb991f0df61f73b3fd620c315e060a17fe98 [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;
Christopher Faulet67a23452020-05-05 18:10:01 +0200597 case TCPCHK_EXPECT_STRING_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Christopher Faulet67a23452020-05-05 18:10:01 +0200600 case TCPCHK_EXPECT_BINARY_REGEX:
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +0200603 case TCPCHK_EXPECT_STRING_LF:
604 chunk_appendf(chk, " (expect log-format string)");
605 break;
606 case TCPCHK_EXPECT_BINARY_LF:
607 chunk_appendf(chk, " (expect log-format binary)");
608 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200609 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200610 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
Christopher Faulet67a23452020-05-05 18:10:01 +0200612 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
Christopher Faulete5870d82020-04-15 11:32:03 +0200613 chunk_appendf(chk, " (expect HTTP status regex)");
614 break;
Christopher Faulet39708192020-05-05 10:47:36 +0200615 case TCPCHK_EXPECT_HTTP_HEADER:
616 chunk_appendf(chk, " (expect HTTP header pattern)");
617 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200618 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200619 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200620 break;
Christopher Faulet67a23452020-05-05 18:10:01 +0200621 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
Christopher Faulete5870d82020-04-15 11:32:03 +0200622 chunk_appendf(chk, " (expect HTTP body regex)");
623 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +0200624 case TCPCHK_EXPECT_HTTP_BODY_LF:
625 chunk_appendf(chk, " (expect log-format HTTP body)");
626 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200627 case TCPCHK_EXPECT_CUSTOM:
628 chunk_appendf(chk, " (expect custom function)");
629 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100630 case TCPCHK_EXPECT_UNDEF:
631 chunk_appendf(chk, " (undefined expect!)");
632 break;
633 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200634 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200635 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200636 chunk_appendf(chk, " (send)");
637 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200638
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200639 if (check->current_step && check->current_step->comment)
640 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200641 }
642 }
643
Willy Tarreau00149122017-10-04 18:05:01 +0200644 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100645 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200646 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
647 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100648 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200649 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
650 chk->area);
651 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100652 }
653 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100654 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200655 chunk_printf(&trash, "%s%s", strerror(errno),
656 chk->area);
657 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100658 }
659 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200660 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100661 }
662 }
663
Willy Tarreau00149122017-10-04 18:05:01 +0200664 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200665 /* NOTE: this is reported after <fall> tries */
666 chunk_printf(chk, "No port available for the TCP connection");
667 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
668 }
669
Willy Tarreau00149122017-10-04 18:05:01 +0200670 if (!conn) {
671 /* connection allocation error before the connection was established */
672 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
673 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100674 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100675 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200676 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100677 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
678 else if (expired)
679 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200680
681 /*
682 * might be due to a server IP change.
683 * Let's trigger a DNS resolution if none are currently running.
684 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100685 if (check->server)
686 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200687
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100688 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100689 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100690 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200691 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100692 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
693 else if (expired)
694 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
695 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200696 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100697 /* I/O error after connection was established and before we could diagnose */
698 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
699 }
700 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200701 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
702
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100703 /* connection established but expired check */
Christopher Faulet1941bab2020-05-05 07:55:50 +0200704 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT &&
705 check->current_step->expect.tout_status != HCHK_STATUS_UNKNOWN)
Christopher Faulet811f78c2020-04-01 11:10:27 +0200706 tout = check->current_step->expect.tout_status;
707 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100708 }
709
710 return;
711}
712
Willy Tarreaubaaee002006-06-26 02:48:02 +0200713
Christopher Faulet61cc8522020-04-20 14:54:42 +0200714/**************************************************************************/
715/*************** Init/deinit tcp-check rules and ruleset ******************/
716/**************************************************************************/
717/* Releases memory allocated for a log-format string */
718static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100721
Christopher Faulet61cc8522020-04-20 14:54:42 +0200722 list_for_each_entry_safe(lf, lfb, fmt, list) {
723 LIST_DEL(&lf->list);
724 release_sample_expr(lf->expr);
725 free(lf->arg);
726 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100727 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200728}
729
Christopher Faulet61cc8522020-04-20 14:54:42 +0200730/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
731static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 if (!hdr)
734 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200735
Christopher Faulet61cc8522020-04-20 14:54:42 +0200736 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200737 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200738 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for an HTTP header list used in a tcp-check send
742 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200743 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200744static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200745{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200746 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200747
Christopher Faulet61cc8522020-04-20 14:54:42 +0200748 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
749 LIST_DEL(&hdr->list);
750 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200751 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200752}
753
Christopher Faulet61cc8522020-04-20 14:54:42 +0200754/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
755 * tcp-check was allocated using a memory pool (it is used to instantiate email
756 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200757 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200758static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200759{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200760 if (!rule)
761 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200762
Christopher Faulet61cc8522020-04-20 14:54:42 +0200763 free(rule->comment);
764 switch (rule->action) {
765 case TCPCHK_ACT_SEND:
766 switch (rule->send.type) {
767 case TCPCHK_SEND_STRING:
768 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200769 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200770 break;
771 case TCPCHK_SEND_STRING_LF:
772 case TCPCHK_SEND_BINARY_LF:
773 free_tcpcheck_fmt(&rule->send.fmt);
774 break;
775 case TCPCHK_SEND_HTTP:
776 free(rule->send.http.meth.str.area);
777 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200778 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200779 else
780 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200781 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200782 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
783 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200784 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200785 else
786 free_tcpcheck_fmt(&rule->send.http.body_fmt);
787 break;
788 case TCPCHK_SEND_UNDEF:
789 break;
790 }
791 break;
792 case TCPCHK_ACT_EXPECT:
793 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
794 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
795 release_sample_expr(rule->expect.status_expr);
796 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200797 case TCPCHK_EXPECT_HTTP_STATUS:
798 free(rule->expect.codes.codes);
799 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200800 case TCPCHK_EXPECT_STRING:
801 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200802 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200803 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200804 break;
Christopher Faulet67a23452020-05-05 18:10:01 +0200805 case TCPCHK_EXPECT_STRING_REGEX:
806 case TCPCHK_EXPECT_BINARY_REGEX:
807 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
808 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200809 regex_free(rule->expect.regex);
810 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +0200811 case TCPCHK_EXPECT_STRING_LF:
812 case TCPCHK_EXPECT_BINARY_LF:
813 case TCPCHK_EXPECT_HTTP_BODY_LF:
814 free_tcpcheck_fmt(&rule->expect.fmt);
815 break;
Christopher Faulet39708192020-05-05 10:47:36 +0200816 case TCPCHK_EXPECT_HTTP_HEADER:
817 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
818 regex_free(rule->expect.hdr.name_re);
819 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
820 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
821 else
822 istfree(&rule->expect.hdr.name);
823
824 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
825 regex_free(rule->expect.hdr.value_re);
826 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
827 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
828 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
829 istfree(&rule->expect.hdr.value);
830 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200831 case TCPCHK_EXPECT_CUSTOM:
832 case TCPCHK_EXPECT_UNDEF:
833 break;
834 }
835 break;
836 case TCPCHK_ACT_CONNECT:
837 free(rule->connect.sni);
838 free(rule->connect.alpn);
839 release_sample_expr(rule->connect.port_expr);
840 break;
841 case TCPCHK_ACT_COMMENT:
842 break;
843 case TCPCHK_ACT_ACTION_KW:
844 free(rule->action_kw.rule);
845 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200846 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200847
848 if (in_pool)
849 pool_free(pool_head_tcpcheck_rule, rule);
850 else
851 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200852}
853
Christopher Faulet61cc8522020-04-20 14:54:42 +0200854/* Creates a tcp-check variable used in preset variables before executing a
855 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100856 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200857static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 var = calloc(1, sizeof(*var));
862 if (var == NULL)
863 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100864
Christopher Fauletb61caf42020-04-21 10:57:42 +0200865 var->name = istdup(name);
866 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867 free(var);
868 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100869 }
Simon Horman98637e52014-06-20 12:30:16 +0900870
Christopher Faulet61cc8522020-04-20 14:54:42 +0200871 LIST_INIT(&var->list);
872 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900873}
874
Christopher Faulet61cc8522020-04-20 14:54:42 +0200875/* Releases memory allocated for a preset tcp-check variable */
876static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900877{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200878 if (!var)
879 return;
880
Christopher Fauletb61caf42020-04-21 10:57:42 +0200881 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200882 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
883 free(var->data.u.str.area);
884 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
885 free(var->data.u.meth.str.area);
886 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900887}
888
Christopher Faulet61cc8522020-04-20 14:54:42 +0200889/* Releases a list of preset tcp-check variables */
890static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900891{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200892 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200893
Christopher Faulet61cc8522020-04-20 14:54:42 +0200894 list_for_each_entry_safe(var, back, vars, list) {
895 LIST_DEL(&var->list);
896 free_tcpcheck_var(var);
897 }
Simon Horman98637e52014-06-20 12:30:16 +0900898}
899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Duplicate a list of preset tcp-check variables */
901int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900902{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200903 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900904
Christopher Faulet61cc8522020-04-20 14:54:42 +0200905 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200906 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200907 if (!new)
908 goto error;
909 new->data.type = var->data.type;
910 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
911 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
912 goto error;
913 if (var->data.type == SMP_T_STR)
914 new->data.u.str.area[new->data.u.str.data] = 0;
915 }
916 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
917 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
918 goto error;
919 new->data.u.str.area[new->data.u.str.data] = 0;
920 new->data.u.meth.meth = var->data.u.meth.meth;
921 }
922 else
923 new->data.u = var->data.u;
924 LIST_ADDQ(dst, &new->list);
925 }
926 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900927
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928 error:
929 free(new);
930 return 0;
931}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200932
Christopher Faulet61cc8522020-04-20 14:54:42 +0200933/* Looks for a shared tcp-check ruleset given its name. */
934static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
935{
936 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200937 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900938
Christopher Fauletd7cee712020-04-21 13:45:00 +0200939 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
940 if (node) {
941 rs = container_of(node, typeof(*rs), node);
942 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 }
944 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900945}
946
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947/* Creates a new shared tcp-check ruleset */
948static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900949{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900951
Christopher Faulet61cc8522020-04-20 14:54:42 +0200952 rs = calloc(1, sizeof(*rs));
953 if (rs == NULL)
954 return NULL;
955
Christopher Fauletd7cee712020-04-21 13:45:00 +0200956 rs->node.key = strdup(name);
957 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200958 free(rs);
959 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900960 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200961
Christopher Faulet61cc8522020-04-20 14:54:42 +0200962 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200963 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900965}
966
Christopher Faulet61cc8522020-04-20 14:54:42 +0200967/* Releases memory allocated by a tcp-check ruleset. */
968static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900969{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 struct tcpcheck_rule *r, *rb;
971 if (!rs)
972 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200973
Christopher Fauletd7cee712020-04-21 13:45:00 +0200974 ebpt_delete(&rs->node);
975 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976 list_for_each_entry_safe(r, rb, &rs->rules, list) {
977 LIST_DEL(&r->list);
978 free_tcpcheck(r, 0);
979 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900981}
982
Christopher Faulet61cc8522020-04-20 14:54:42 +0200983
984/**************************************************************************/
985/**************** Everything about tcp-checks execution *******************/
986/**************************************************************************/
987/* Returns the id of a step in a tcp-check ruleset */
988static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200989{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200990 if (!rule)
991 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900992
Christopher Faulet61cc8522020-04-20 14:54:42 +0200993 /* no last started step => first step */
994 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900995 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900996
Christopher Faulet61cc8522020-04-20 14:54:42 +0200997 /* last step is the first implicit connect */
998 if (rule->index == 0 &&
999 rule->action == TCPCHK_ACT_CONNECT &&
1000 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
1001 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001002
Christopher Faulet61cc8522020-04-20 14:54:42 +02001003 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +09001004}
1005
Christopher Faulet61cc8522020-04-20 14:54:42 +02001006/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
1007 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001008 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001010{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001011 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001012
Christopher Faulet61cc8522020-04-20 14:54:42 +02001013 list_for_each_entry(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;
1018}
Cyril Bontéac92a062014-12-27 22:28:38 +01001019
Christopher Faulet61cc8522020-04-20 14:54:42 +02001020/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
1021 * NULL if none was found.
1022 */
1023static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
1024{
1025 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 list_for_each_entry_rev(r, rules->list, list) {
1028 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1029 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001030 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001031 return NULL;
1032}
Cyril Bontéac92a062014-12-27 22:28:38 +01001033
Christopher Faulet61cc8522020-04-20 14:54:42 +02001034/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1035 * <start> or NULL if non was found. If <start> is NULL, it relies on
1036 * get_first_tcpcheck_rule().
1037 */
1038static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1039{
1040 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041
Christopher Faulet61cc8522020-04-20 14:54:42 +02001042 if (!start)
1043 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001044
Christopher Faulet61cc8522020-04-20 14:54:42 +02001045 r = LIST_NEXT(&start->list, typeof(r), list);
1046 list_for_each_entry_from(r, rules->list, list) {
1047 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1048 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001049 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001050 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001051}
Simon Horman98637e52014-06-20 12:30:16 +09001052
Simon Horman98637e52014-06-20 12:30:16 +09001053
Christopher Faulet61cc8522020-04-20 14:54:42 +02001054/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1055static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1056 int match, struct ist info)
1057{
1058 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001059
Christopher Faulet61cc8522020-04-20 14:54:42 +02001060 /* Follows these step to produce the info message:
1061 * 1. if info field is already provided, copy it
1062 * 2. if the expect rule provides an onerror log-format string,
1063 * use it to produce the message
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001064 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
Christopher Faulet61cc8522020-04-20 14:54:42 +02001065 * 4. Otherwise produce the generic tcp-check info message
1066 */
1067 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001068 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001069 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001070 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001071 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1072 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1073 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001074 }
Simon Horman98637e52014-06-20 12:30:16 +09001075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 if (check->type == PR_O2_TCPCHK_CHK &&
1077 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1078 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001079
Christopher Faulet61cc8522020-04-20 14:54:42 +02001080 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1081 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001082 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001083 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1084 break;
1085 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001086 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001087 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001088 tcpcheck_get_step_id(check, rule));
1089 break;
1090 case TCPCHK_EXPECT_BINARY:
1091 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1092 break;
Christopher Faulet67a23452020-05-05 18:10:01 +02001093 case TCPCHK_EXPECT_STRING_REGEX:
1094 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1095 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001096 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1097 break;
Christopher Faulet67a23452020-05-05 18:10:01 +02001098 case TCPCHK_EXPECT_BINARY_REGEX:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001099 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001100 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +02001101 case TCPCHK_EXPECT_STRING_LF:
1102 case TCPCHK_EXPECT_HTTP_BODY_LF:
1103 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
1104 break;
1105 case TCPCHK_EXPECT_BINARY_LF:
1106 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
1107 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001108 case TCPCHK_EXPECT_CUSTOM:
1109 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1110 break;
Christopher Faulet39708192020-05-05 10:47:36 +02001111 case TCPCHK_EXPECT_HTTP_HEADER:
1112 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 case TCPCHK_EXPECT_UNDEF:
1114 /* Should never happen. */
1115 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001116 }
1117
Christopher Faulet61cc8522020-04-20 14:54:42 +02001118 comment:
1119 /* If the failing expect rule provides a comment, it is concatenated to
1120 * the info message.
1121 */
1122 if (rule->comment) {
1123 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001124 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001125 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001126
Christopher Faulet61cc8522020-04-20 14:54:42 +02001127 /* Finally, the check status code is set if the failing expect rule
1128 * defines a status expression.
1129 */
1130 if (rule->expect.status_expr) {
1131 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
Christopher Faulet78f371e2020-04-30 09:38:08 +02001132 rule->expect.status_expr, SMP_T_STR);
1133
1134 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
1135 sample_casts[smp->data.type][SMP_T_SINT](smp))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001137 }
Simon Horman98637e52014-06-20 12:30:16 +09001138
Christopher Faulet61cc8522020-04-20 14:54:42 +02001139 *(b_tail(msg)) = '\0';
1140}
Cyril Bontéac92a062014-12-27 22:28:38 +01001141
Christopher Faulet61cc8522020-04-20 14:54:42 +02001142/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1143static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1144 struct ist info)
1145{
1146 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001147
Christopher Faulet61cc8522020-04-20 14:54:42 +02001148 /* Follows these step to produce the info message:
1149 * 1. if info field is already provided, copy it
1150 * 2. if the expect rule provides an onsucces log-format string,
1151 * use it to produce the message
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001152 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 * 4. Otherwise produce the generic tcp-check info message
1154 */
1155 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001156 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001157 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1158 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1159 &rule->expect.onsuccess_fmt);
1160 else if (check->type == PR_O2_TCPCHK_CHK &&
1161 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1162 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001163
Christopher Faulet61cc8522020-04-20 14:54:42 +02001164 /* Finally, the check status code is set if the expect rule defines a
1165 * status expression.
1166 */
1167 if (rule->expect.status_expr) {
1168 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
Christopher Faulet78f371e2020-04-30 09:38:08 +02001169 rule->expect.status_expr, SMP_T_STR);
1170
1171 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
1172 sample_casts[smp->data.type][SMP_T_SINT](smp))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001173 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001174 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001175
1176 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001177}
1178
Christopher Faulet61cc8522020-04-20 14:54:42 +02001179/* Builds the server state header used by HTTP health-checks */
1180static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001181{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001182 int sv_state;
1183 int ratio;
1184 char addr[46];
1185 char port[6];
1186 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1187 "UP %d/%d", "UP",
1188 "NOLB %d/%d", "NOLB",
1189 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001190
Christopher Faulet61cc8522020-04-20 14:54:42 +02001191 if (!(s->check.state & CHK_ST_ENABLED))
1192 sv_state = 6;
1193 else if (s->cur_state != SRV_ST_STOPPED) {
1194 if (s->check.health == s->check.rise + s->check.fall - 1)
1195 sv_state = 3; /* UP */
1196 else
1197 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001198
Christopher Faulet61cc8522020-04-20 14:54:42 +02001199 if (s->cur_state == SRV_ST_STOPPING)
1200 sv_state += 2;
1201 } else {
1202 if (s->check.health)
1203 sv_state = 1; /* going up */
1204 else
1205 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001206 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001207
Christopher Faulet61cc8522020-04-20 14:54:42 +02001208 chunk_appendf(buf, srv_hlt_st[sv_state],
1209 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1210 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001211
Christopher Faulet61cc8522020-04-20 14:54:42 +02001212 addr_to_str(&s->addr, addr, sizeof(addr));
1213 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1214 snprintf(port, sizeof(port), "%u", s->svc_port);
1215 else
1216 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1219 addr, port, s->proxy->id, s->id,
1220 global.node,
1221 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1222 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1223 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1224 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001225
Christopher Faulet61cc8522020-04-20 14:54:42 +02001226 if ((s->cur_state == SRV_ST_STARTING) &&
1227 now.tv_sec < s->last_change + s->slowstart &&
1228 now.tv_sec >= s->last_change) {
1229 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1230 chunk_appendf(buf, "; throttle=%d%%", ratio);
1231 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001232
Christopher Faulet61cc8522020-04-20 14:54:42 +02001233 return b_data(buf);
1234}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236/* Internal functions to parse and validate a MySQL packet in the context of an
1237 * expect rule. It start to parse the input buffer at the offset <offset>. If
1238 * <last_read> is set, no more data are expected.
1239 */
1240static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1241 unsigned int offset, int last_read)
1242{
1243 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1244 enum healthcheck_status status;
1245 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001246 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001248
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001249
Christopher Faulet61cc8522020-04-20 14:54:42 +02001250 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001251 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001252 if (!last_read)
1253 goto wait_more_data;
1254
1255 /* invalid length or truncated response */
1256 status = HCHK_STATUS_L7RSP;
1257 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001258 }
1259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1261 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1262 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001263
Christopher Faulet61cc8522020-04-20 14:54:42 +02001264 if (b_data(&check->bi) < offset+plen+4) {
1265 if (!last_read)
1266 goto wait_more_data;
1267
1268 /* invalid length or truncated response */
1269 status = HCHK_STATUS_L7RSP;
1270 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001271 }
Simon Horman98637e52014-06-20 12:30:16 +09001272
Christopher Faulet61cc8522020-04-20 14:54:42 +02001273 if (*b_peek(&check->bi, offset+4) == '\xff') {
1274 /* MySQL Error packet always begin with field_count = 0xff */
1275 status = HCHK_STATUS_L7STS;
1276 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1277 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1278 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1279 goto error;
1280 }
Simon Horman98637e52014-06-20 12:30:16 +09001281
Christopher Faulet61cc8522020-04-20 14:54:42 +02001282 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1283 /* Not the last rule, continue */
1284 goto out;
1285 }
Simon Horman98637e52014-06-20 12:30:16 +09001286
Christopher Faulet61cc8522020-04-20 14:54:42 +02001287 /* We set the MySQL Version in description for information purpose
1288 * FIXME : it can be cool to use MySQL Version for other purpose,
1289 * like mark as down old MySQL server.
1290 */
Christopher Faulet1941bab2020-05-05 07:55:50 +02001291 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1292 set_server_check_status(check, status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001293
Christopher Faulet61cc8522020-04-20 14:54:42 +02001294 out:
1295 free_trash_chunk(msg);
1296 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001297
Christopher Faulet61cc8522020-04-20 14:54:42 +02001298 error:
1299 ret = TCPCHK_EVAL_STOP;
1300 check->code = err;
1301 msg = alloc_trash_chunk();
1302 if (msg)
1303 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1304 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1305 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001306
Christopher Faulet61cc8522020-04-20 14:54:42 +02001307 wait_more_data:
1308 ret = TCPCHK_EVAL_WAIT;
1309 goto out;
1310}
Simon Horman98637e52014-06-20 12:30:16 +09001311
Christopher Faulet61cc8522020-04-20 14:54:42 +02001312/* Custom tcp-check expect function to parse and validate the MySQL initial
1313 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1314 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1315 * error occurred.
1316 */
1317static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1318{
1319 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1320}
Simon Horman98637e52014-06-20 12:30:16 +09001321
Christopher Faulet61cc8522020-04-20 14:54:42 +02001322/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1323 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1324 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1325 * an error occurred.
1326 */
1327static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1328{
1329 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001330
Christopher Faulet61cc8522020-04-20 14:54:42 +02001331 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1332 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1333 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001334
Christopher Faulet61cc8522020-04-20 14:54:42 +02001335 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1336}
Simon Horman98637e52014-06-20 12:30:16 +09001337
Christopher Faulet61cc8522020-04-20 14:54:42 +02001338/* Custom tcp-check expect function to parse and validate the LDAP bind response
1339 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1340 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1341 * error occurred.
1342 */
1343static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1344{
1345 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1346 enum healthcheck_status status;
1347 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001348 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001349 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001350
Christopher Faulet61cc8522020-04-20 14:54:42 +02001351 /* Check if the server speaks LDAP (ASN.1/BER)
1352 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1353 * http://tools.ietf.org/html/rfc4511
1354 */
1355 /* size of LDAPMessage */
1356 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1359 * messageID: 0x02 0x01 0x01: INTEGER 1
1360 * protocolOp: 0x61: bindResponse
1361 */
1362 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1363 status = HCHK_STATUS_L7RSP;
1364 desc = ist("Not LDAPv3 protocol");
1365 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001366 }
Simon Horman98637e52014-06-20 12:30:16 +09001367
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 /* size of bindResponse */
1369 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1372 * ldapResult: 0x0a 0x01: ENUMERATION
1373 */
1374 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1375 status = HCHK_STATUS_L7RSP;
1376 desc = ist("Not LDAPv3 protocol");
1377 goto error;
1378 }
Simon Horman98637e52014-06-20 12:30:16 +09001379
Christopher Faulet61cc8522020-04-20 14:54:42 +02001380 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1381 * resultCode
1382 */
1383 check->code = *(b_head(&check->bi) + msglen + 9);
1384 if (check->code) {
1385 status = HCHK_STATUS_L7STS;
1386 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1387 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001388 }
1389
Christopher Faulet1941bab2020-05-05 07:55:50 +02001390 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1391 set_server_check_status(check, status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001392
Christopher Faulet61cc8522020-04-20 14:54:42 +02001393 out:
1394 free_trash_chunk(msg);
1395 return ret;
1396
1397 error:
1398 ret = TCPCHK_EVAL_STOP;
1399 msg = alloc_trash_chunk();
1400 if (msg)
1401 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1402 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1403 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001404}
1405
Christopher Faulet61cc8522020-04-20 14:54:42 +02001406/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1407 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1408 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001409 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001410static 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 +02001411{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001412 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1413 enum healthcheck_status status;
1414 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001415 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001416 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001417
Willy Tarreaubaaee002006-06-26 02:48:02 +02001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 memcpy(&framesz, b_head(&check->bi), 4);
1420 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001421
Christopher Faulet61cc8522020-04-20 14:54:42 +02001422 if (!last_read && b_data(&check->bi) < (4+framesz))
1423 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001424
Christopher Faulet61cc8522020-04-20 14:54:42 +02001425 memset(b_orig(&trash), 0, b_size(&trash));
1426 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1427 status = HCHK_STATUS_L7RSP;
1428 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1429 goto error;
1430 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001431
Christopher Faulet1941bab2020-05-05 07:55:50 +02001432 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
1433 set_server_check_status(check, status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001434
Christopher Faulet61cc8522020-04-20 14:54:42 +02001435 out:
1436 free_trash_chunk(msg);
1437 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001438
Christopher Faulet61cc8522020-04-20 14:54:42 +02001439 error:
1440 ret = TCPCHK_EVAL_STOP;
1441 msg = alloc_trash_chunk();
1442 if (msg)
1443 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1444 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1445 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 wait_more_data:
1448 ret = TCPCHK_EVAL_WAIT;
1449 goto out;
1450}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001451
Christopher Faulet61cc8522020-04-20 14:54:42 +02001452/* Custom tcp-check expect function to parse and validate the agent-check
1453 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1454 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1455 */
1456static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1457{
1458 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1459 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1460 const char *hs = NULL; /* health status */
1461 const char *as = NULL; /* admin status */
1462 const char *ps = NULL; /* performance status */
1463 const char *cs = NULL; /* maxconn */
1464 const char *err = NULL; /* first error to report */
1465 const char *wrn = NULL; /* first warning to report */
1466 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001467
Christopher Faulet61cc8522020-04-20 14:54:42 +02001468 /* We're getting an agent check response. The agent could
1469 * have been disabled in the mean time with a long check
1470 * still pending. It is important that we ignore the whole
1471 * response.
1472 */
1473 if (!(check->state & CHK_ST_ENABLED))
1474 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* The agent supports strings made of a single line ended by the
1477 * first CR ('\r') or LF ('\n'). This line is composed of words
1478 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1479 * line may optionally contained a description of a state change
1480 * after a sharp ('#'), which is only considered if a health state
1481 * is announced.
1482 *
1483 * Words may be composed of :
1484 * - a numeric weight suffixed by the percent character ('%').
1485 * - a health status among "up", "down", "stopped", and "fail".
1486 * - an admin status among "ready", "drain", "maint".
1487 *
1488 * These words may appear in any order. If multiple words of the
1489 * same category appear, the last one wins.
1490 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001491
Christopher Faulet61cc8522020-04-20 14:54:42 +02001492 p = b_head(&check->bi);
1493 while (*p && *p != '\n' && *p != '\r')
1494 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001495
Christopher Faulet61cc8522020-04-20 14:54:42 +02001496 if (!*p) {
1497 if (!last_read)
1498 goto wait_more_data;
1499
1500 /* at least inform the admin that the agent is mis-behaving */
1501 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1502 goto out;
1503 }
1504
1505 *p = 0;
1506 cmd = b_head(&check->bi);
1507
1508 while (*cmd) {
1509 /* look for next word */
1510 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1511 cmd++;
1512 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001513 }
1514
Christopher Faulet61cc8522020-04-20 14:54:42 +02001515 if (*cmd == '#') {
1516 /* this is the beginning of a health status description,
1517 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001518 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001519 cmd++;
1520 while (*cmd == '\t' || *cmd == ' ')
1521 cmd++;
1522 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001523 }
1524
Christopher Faulet61cc8522020-04-20 14:54:42 +02001525 /* find the end of the word so that we have a null-terminated
1526 * word between <cmd> and <p>.
1527 */
1528 p = cmd + 1;
1529 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1530 p++;
1531 if (*p)
1532 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001533
Christopher Faulet61cc8522020-04-20 14:54:42 +02001534 /* first, health statuses */
1535 if (strcasecmp(cmd, "up") == 0) {
1536 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1537 status = HCHK_STATUS_L7OKD;
1538 hs = cmd;
1539 }
1540 else if (strcasecmp(cmd, "down") == 0) {
1541 check->server->check.health = 0;
1542 status = HCHK_STATUS_L7STS;
1543 hs = cmd;
1544 }
1545 else if (strcasecmp(cmd, "stopped") == 0) {
1546 check->server->check.health = 0;
1547 status = HCHK_STATUS_L7STS;
1548 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001549 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001550 else if (strcasecmp(cmd, "fail") == 0) {
1551 check->server->check.health = 0;
1552 status = HCHK_STATUS_L7STS;
1553 hs = cmd;
1554 }
1555 /* admin statuses */
1556 else if (strcasecmp(cmd, "ready") == 0) {
1557 as = cmd;
1558 }
1559 else if (strcasecmp(cmd, "drain") == 0) {
1560 as = cmd;
1561 }
1562 else if (strcasecmp(cmd, "maint") == 0) {
1563 as = cmd;
1564 }
1565 /* try to parse a weight here and keep the last one */
1566 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1567 ps = cmd;
1568 }
1569 /* try to parse a maxconn here */
1570 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1571 cs = cmd;
1572 }
1573 else {
1574 /* keep a copy of the first error */
1575 if (!err)
1576 err = cmd;
1577 }
1578 /* skip to next word */
1579 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001580 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001581 /* here, cmd points either to \0 or to the beginning of a
1582 * description. Skip possible leading spaces.
1583 */
1584 while (*cmd == ' ' || *cmd == '\n')
1585 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 /* First, update the admin status so that we avoid sending other
1588 * possibly useless warnings and can also update the health if
1589 * present after going back up.
1590 */
1591 if (as) {
1592 if (strcasecmp(as, "drain") == 0)
1593 srv_adm_set_drain(check->server);
1594 else if (strcasecmp(as, "maint") == 0)
1595 srv_adm_set_maint(check->server);
1596 else
1597 srv_adm_set_ready(check->server);
1598 }
Simon Horman98637e52014-06-20 12:30:16 +09001599
Christopher Faulet61cc8522020-04-20 14:54:42 +02001600 /* now change weights */
1601 if (ps) {
1602 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001603
Christopher Faulet61cc8522020-04-20 14:54:42 +02001604 msg = server_parse_weight_change_request(check->server, ps);
1605 if (!wrn || !*wrn)
1606 wrn = msg;
1607 }
Simon Horman98637e52014-06-20 12:30:16 +09001608
Christopher Faulet61cc8522020-04-20 14:54:42 +02001609 if (cs) {
1610 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001611
Christopher Faulet61cc8522020-04-20 14:54:42 +02001612 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001613
Christopher Faulet61cc8522020-04-20 14:54:42 +02001614 msg = server_parse_maxconn_change_request(check->server, cs);
1615 if (!wrn || !*wrn)
1616 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001617 }
1618
Christopher Faulet61cc8522020-04-20 14:54:42 +02001619 /* and finally health status */
1620 if (hs) {
1621 /* We'll report some of the warnings and errors we have
1622 * here. Down reports are critical, we leave them untouched.
1623 * Lack of report, or report of 'UP' leaves the room for
1624 * ERR first, then WARN.
1625 */
1626 const char *msg = cmd;
1627 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001628
Christopher Faulet61cc8522020-04-20 14:54:42 +02001629 if (!*msg || status == HCHK_STATUS_L7OKD) {
1630 if (err && *err)
1631 msg = err;
1632 else if (wrn && *wrn)
1633 msg = wrn;
1634 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001635
Christopher Faulet61cc8522020-04-20 14:54:42 +02001636 t = get_trash_chunk();
1637 chunk_printf(t, "via agent : %s%s%s%s",
1638 hs, *msg ? " (" : "",
1639 msg, *msg ? ")" : "");
1640 set_server_check_status(check, status, t->area);
1641 }
1642 else if (err && *err) {
1643 /* No status change but we'd like to report something odd.
1644 * Just report the current state and copy the message.
1645 */
1646 chunk_printf(&trash, "agent reports an error : %s", err);
1647 set_server_check_status(check, status/*check->status*/, trash.area);
1648 }
1649 else if (wrn && *wrn) {
1650 /* No status change but we'd like to report something odd.
1651 * Just report the current state and copy the message.
1652 */
1653 chunk_printf(&trash, "agent warns : %s", wrn);
1654 set_server_check_status(check, status/*check->status*/, trash.area);
1655 }
1656 else
1657 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001658
Christopher Faulet61cc8522020-04-20 14:54:42 +02001659 out:
1660 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001661
Christopher Faulet61cc8522020-04-20 14:54:42 +02001662 wait_more_data:
1663 ret = TCPCHK_EVAL_WAIT;
1664 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001665}
1666
Christopher Faulet61cc8522020-04-20 14:54:42 +02001667/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1668 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1669 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001672{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001673 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1674 struct tcpcheck_connect *connect = &rule->connect;
1675 struct proxy *proxy = check->proxy;
1676 struct server *s = check->server;
1677 struct task *t = check->task;
1678 struct conn_stream *cs;
1679 struct connection *conn = NULL;
1680 struct protocol *proto;
1681 struct xprt_ops *xprt;
Christopher Faulet2fabd9d2020-05-09 14:46:43 +02001682 struct tcpcheck_rule *next;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001683 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001684
Christopher Faulet61cc8522020-04-20 14:54:42 +02001685 /* For a connect action we'll create a new connection. We may also have
1686 * to kill a previous one. But we don't want to leave *without* a
1687 * connection if we came here from the connection layer, hence with a
1688 * connection. Thus we'll proceed in the following order :
1689 * 1: close but not release previous connection (handled by the caller)
1690 * 2: try to get a new connection
1691 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001692 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001693
Christopher Faulet61cc8522020-04-20 14:54:42 +02001694 /* 2- prepare new connection */
1695 cs = cs_new(NULL);
1696 if (!cs) {
1697 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1698 tcpcheck_get_step_id(check, rule));
1699 if (rule->comment)
1700 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1701 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1702 ret = TCPCHK_EVAL_STOP;
1703 goto out;
1704 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001705
Christopher Faulet61cc8522020-04-20 14:54:42 +02001706 /* 3- release and replace the old one on success */
1707 if (check->cs) {
1708 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001709 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1710 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001711
1712 /* We may have been scheduled to run, and the I/O handler
1713 * expects to have a cs, so remove the tasklet
1714 */
1715 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1716 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001717 }
1718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001720
Christopher Faulet61cc8522020-04-20 14:54:42 +02001721 check->cs = cs;
1722 conn = cs->conn;
1723 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001724
Christopher Faulet61cc8522020-04-20 14:54:42 +02001725 /* Maybe there were an older connection we were waiting on */
1726 check->wait_list.events = 0;
1727 conn->target = s ? &s->obj_type : &proxy->obj_type;
1728
1729 /* no client address */
1730 if (!sockaddr_alloc(&conn->dst)) {
1731 status = SF_ERR_RESOURCE;
1732 goto fail_check;
1733 }
1734
1735 /* connect to the connect rule addr if specified, otherwise the check
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001736 * addr if specified on the server. otherwise, use the server addr (it
1737 * MUST exist at this step).
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001738 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 *conn->dst = (is_addr(&connect->addr)
1740 ? connect->addr
1741 : (is_addr(&check->addr) ? check->addr : s->addr));
1742 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001743
Christopher Faulet61cc8522020-04-20 14:54:42 +02001744 port = 0;
1745 if (!port && connect->port)
1746 port = connect->port;
1747 if (!port && connect->port_expr) {
1748 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001749
Christopher Faulet61cc8522020-04-20 14:54:42 +02001750 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1751 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1752 connect->port_expr, SMP_T_SINT);
1753 if (smp)
1754 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001755 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001756 if (!port && is_inet_addr(&connect->addr))
1757 port = get_host_port(&connect->addr);
1758 if (!port && check->port)
1759 port = check->port;
1760 if (!port && is_inet_addr(&check->addr))
1761 port = get_host_port(&check->addr);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001762 if (!port) {
1763 /* The server MUST exist here */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001764 port = s->svc_port;
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001765 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001766 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001767
Christopher Faulet61cc8522020-04-20 14:54:42 +02001768 xprt = ((connect->options & TCPCHK_OPT_SSL)
1769 ? xprt_get(XPRT_SSL)
1770 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001771
Christopher Faulet61cc8522020-04-20 14:54:42 +02001772 conn_prepare(conn, proto, xprt);
1773 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001774
Christopher Faulet61cc8522020-04-20 14:54:42 +02001775 status = SF_ERR_INTERNAL;
Christopher Faulet2fabd9d2020-05-09 14:46:43 +02001776 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001777 if (proto && proto->connect) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001778 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001779
Christopher Faulet61cc8522020-04-20 14:54:42 +02001780 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1781 flags |= CONNECT_HAS_DATA;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001782 if (!next || next->action != TCPCHK_ACT_EXPECT)
1783 flags |= CONNECT_DELACK_ALWAYS;
1784 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001785 }
1786
Christopher Faulet61cc8522020-04-20 14:54:42 +02001787 if (status != SF_ERR_NONE)
1788 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001789
Christopher Faulet61cc8522020-04-20 14:54:42 +02001790 conn->flags |= CO_FL_PRIVATE;
1791 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001792
Christopher Faulet61cc8522020-04-20 14:54:42 +02001793 /* The mux may be initialized now if there isn't server attached to the
1794 * check (email alerts) or if there is a mux proto specified or if there
1795 * is no alpn.
1796 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001797 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1798 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001799 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001800
Christopher Faulet61cc8522020-04-20 14:54:42 +02001801 if (connect->mux_proto)
1802 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001803 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001804 mux_ops = check->mux_proto->mux;
1805 else {
1806 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1807 ? PROTO_MODE_HTTP
1808 : PROTO_MODE_TCP);
1809
1810 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001811 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001812 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1813 status = SF_ERR_INTERNAL;
1814 goto fail_check;
1815 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001816 }
1817
Christopher Faulet61cc8522020-04-20 14:54:42 +02001818#ifdef USE_OPENSSL
1819 if (connect->sni)
1820 ssl_sock_set_servername(conn, connect->sni);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001821 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001822 ssl_sock_set_servername(conn, s->check.sni);
1823
1824 if (connect->alpn)
1825 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001826 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001827 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1828#endif
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001829 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001830 conn->send_proxy_ofs = 1;
1831 conn->flags |= CO_FL_SOCKS4;
1832 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001833 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 +02001834 conn->send_proxy_ofs = 1;
1835 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001836 }
1837
Christopher Faulet61cc8522020-04-20 14:54:42 +02001838 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1839 conn->send_proxy_ofs = 1;
1840 conn->flags |= CO_FL_SEND_PROXY;
1841 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001842 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 +02001843 conn->send_proxy_ofs = 1;
1844 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001845 }
1846
Christopher Faulet61cc8522020-04-20 14:54:42 +02001847 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1848 /* Some servers don't like reset on close */
1849 fdtab[cs->conn->handle.fd].linger_risk = 0;
1850 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001851
Christopher Faulet61cc8522020-04-20 14:54:42 +02001852 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1853 if (xprt_add_hs(conn) < 0)
1854 status = SF_ERR_RESOURCE;
1855 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001856
Christopher Faulet61cc8522020-04-20 14:54:42 +02001857 fail_check:
1858 /* It can return one of :
1859 * - SF_ERR_NONE if everything's OK
1860 * - SF_ERR_SRVTO if there are no more servers
1861 * - SF_ERR_SRVCL if the connection was refused by the server
1862 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1863 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1864 * - SF_ERR_INTERNAL for any other purely internal errors
1865 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1866 * Note that we try to prevent the network stack from sending the ACK during the
1867 * connect() when a pure TCP check is used (without PROXY protocol).
1868 */
1869 switch (status) {
1870 case SF_ERR_NONE:
1871 /* we allow up to min(inter, timeout.connect) for a connection
1872 * to establish but only when timeout.check is set as it may be
1873 * to short for a full check otherwise
1874 */
1875 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001876
Christopher Faulet61cc8522020-04-20 14:54:42 +02001877 if (proxy->timeout.check && proxy->timeout.connect) {
1878 int t_con = tick_add(now_ms, proxy->timeout.connect);
1879 t->expire = tick_first(t->expire, t_con);
1880 }
1881 break;
1882 case SF_ERR_SRVTO: /* ETIMEDOUT */
1883 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1884 case SF_ERR_PRXCOND:
1885 case SF_ERR_RESOURCE:
1886 case SF_ERR_INTERNAL:
1887 chk_report_conn_err(check, errno, 0);
1888 ret = TCPCHK_EVAL_STOP;
1889 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001890 }
1891
Christopher Faulet61cc8522020-04-20 14:54:42 +02001892 /* don't do anything until the connection is established */
1893 if (conn->flags & CO_FL_WAIT_XPRT) {
Christopher Faulet2fabd9d2020-05-09 14:46:43 +02001894 if (next && next->action == TCPCHK_ACT_SEND)
1895 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1896 else
1897 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001898 ret = TCPCHK_EVAL_WAIT;
1899 goto out;
1900 }
1901
1902 out:
1903 if (conn && check->result == CHK_RES_FAILED)
1904 conn->flags |= CO_FL_ERROR;
1905 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001906}
1907
Christopher Faulet61cc8522020-04-20 14:54:42 +02001908/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1909 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1910 * TCPCHK_EVAL_STOP if an error occurred.
1911 */
1912static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001913{
1914 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001915 struct tcpcheck_send *send = &rule->send;
1916 struct conn_stream *cs = check->cs;
1917 struct connection *conn = cs_conn(cs);
1918 struct buffer *tmp = NULL;
1919 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001920
Christopher Faulet61cc8522020-04-20 14:54:42 +02001921 /* reset the read & write buffer */
1922 b_reset(&check->bi);
1923 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001924
Christopher Faulet61cc8522020-04-20 14:54:42 +02001925 switch (send->type) {
1926 case TCPCHK_SEND_STRING:
1927 case TCPCHK_SEND_BINARY:
1928 if (istlen(send->data) >= b_size(&check->bo)) {
1929 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1930 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1931 tcpcheck_get_step_id(check, rule));
1932 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1933 ret = TCPCHK_EVAL_STOP;
1934 goto out;
1935 }
1936 b_putist(&check->bo, send->data);
1937 break;
1938 case TCPCHK_SEND_STRING_LF:
1939 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1940 if (!b_data(&check->bo))
1941 goto out;
1942 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001943 case TCPCHK_SEND_BINARY_LF: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02001944 int len = b_size(&check->bo);
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001945
Christopher Faulet61cc8522020-04-20 14:54:42 +02001946 tmp = alloc_trash_chunk();
1947 if (!tmp)
1948 goto error_lf;
1949 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1950 if (!b_data(tmp))
1951 goto out;
1952 tmp->area[tmp->data] = '\0';
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001953 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001954 goto error_lf;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001955 check->bo.data = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001956 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001957 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001958 case TCPCHK_SEND_HTTP: {
1959 struct htx_sl *sl;
1960 struct ist meth, uri, vsn, clen, body;
1961 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001962
Christopher Faulet61cc8522020-04-20 14:54:42 +02001963 tmp = alloc_trash_chunk();
1964 if (!tmp)
1965 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001966
Christopher Faulet61cc8522020-04-20 14:54:42 +02001967 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1968 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1969 : http_known_methods[send->http.meth.meth]);
Christopher Faulet7c95f5f2020-05-06 15:06:34 +02001970 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1971 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1972 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1973 }
1974 else
1975 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001976 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001977
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001978 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1979 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001980 slflags |= HTX_SL_F_VER_11;
1981 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1982 if (!isttest(send->http.body))
1983 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001984
Christopher Faulet61cc8522020-04-20 14:54:42 +02001985 htx = htx_from_buf(&check->bo);
1986 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1987 if (!sl)
1988 goto error_htx;
1989 sl->info.req.meth = send->http.meth.meth;
Christopher Faulet39959a72020-04-29 13:21:37 +02001990 if (!http_update_host(htx, sl, uri))
1991 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001992
Christopher Faulet61cc8522020-04-20 14:54:42 +02001993 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1994 struct tcpcheck_http_hdr *hdr;
Christopher Faulet39959a72020-04-29 13:21:37 +02001995 struct ist hdr_value;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001996
Christopher Faulet61cc8522020-04-20 14:54:42 +02001997 list_for_each_entry(hdr, &send->http.hdrs, list) {
1998 chunk_reset(tmp);
1999 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
2000 if (!b_data(tmp))
2001 continue;
Christopher Faulet39959a72020-04-29 13:21:37 +02002002 hdr_value = ist2(b_orig(tmp), b_data(tmp));
2003 if (!htx_add_header(htx, hdr->name, hdr_value))
Christopher Faulet61cc8522020-04-20 14:54:42 +02002004 goto error_htx;
Christopher Faulet39959a72020-04-29 13:21:37 +02002005 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
2006 if (!http_update_authority(htx, sl, hdr_value))
2007 goto error_htx;
2008 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002009 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002010
Christopher Faulet61cc8522020-04-20 14:54:42 +02002011 }
2012 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
2013 chunk_reset(tmp);
2014 httpchk_build_status_header(check->server, tmp);
2015 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
2016 goto error_htx;
2017 }
Christopher Faulet574e7bd2020-05-06 15:38:58 +02002018
2019
2020 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2021 chunk_reset(tmp);
2022 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
2023 body = ist2(b_orig(tmp), b_data(tmp));
2024 }
2025 else
2026 body = send->http.body;
2027 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
2028
2029 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
2030 !htx_add_header(htx, ist("Content-length"), clen))
2031 goto error_htx;
2032
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002033
Christopher Faulet61cc8522020-04-20 14:54:42 +02002034 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet574e7bd2020-05-06 15:38:58 +02002035 (istlen(body) && !htx_add_data_atonce(htx, body)) ||
Christopher Faulet61cc8522020-04-20 14:54:42 +02002036 !htx_add_endof(htx, HTX_BLK_EOM))
2037 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002038
Christopher Faulet61cc8522020-04-20 14:54:42 +02002039 htx_to_buf(htx, &check->bo);
2040 break;
2041 }
2042 case TCPCHK_SEND_UNDEF:
2043 /* Should never happen. */
2044 ret = TCPCHK_EVAL_STOP;
2045 goto out;
2046 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002047
Christopher Faulet6d471212020-04-22 11:09:25 +02002048
2049 if (conn->mux->snd_buf(cs, &check->bo,
2050 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02002051 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002052 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02002053 goto out;
2054 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002055 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002056 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002057 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2058 ret = TCPCHK_EVAL_WAIT;
2059 goto out;
2060 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002061
Christopher Faulet61cc8522020-04-20 14:54:42 +02002062 out:
2063 free_trash_chunk(tmp);
2064 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002065
Christopher Faulet61cc8522020-04-20 14:54:42 +02002066 error_htx:
2067 if (htx) {
2068 htx_reset(htx);
2069 htx_to_buf(htx, &check->bo);
2070 }
2071 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
2072 tcpcheck_get_step_id(check, rule));
2073 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2074 ret = TCPCHK_EVAL_STOP;
2075 goto out;
2076
2077 error_lf:
2078 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2079 tcpcheck_get_step_id(check, rule));
2080 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2081 ret = TCPCHK_EVAL_STOP;
2082 goto out;
2083
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002084}
2085
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05002086/* Try to receive data before evaluating a tcp-check expect rule. Returns
2087 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
Christopher Faulet61cc8522020-04-20 14:54:42 +02002088 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2089 * TCPCHK_EVAL_STOP if an error occurred.
2090 */
2091static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002092{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002093 struct conn_stream *cs = check->cs;
2094 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002095 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002096 size_t max, read, cur_read = 0;
2097 int is_empty;
2098 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002099
Christopher Faulet61cc8522020-04-20 14:54:42 +02002100 if (check->wait_list.events & SUB_RETRY_RECV)
2101 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002102
Christopher Faulet61cc8522020-04-20 14:54:42 +02002103 if (cs->flags & CS_FL_EOS)
2104 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002105
Christopher Faulet61cc8522020-04-20 14:54:42 +02002106 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002107
Christopher Faulet61cc8522020-04-20 14:54:42 +02002108 /* prepare to detect if the mux needs more room */
2109 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002110
Christopher Faulet61cc8522020-04-20 14:54:42 +02002111 while ((cs->flags & CS_FL_RCV_MORE) ||
2112 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2113 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2114 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2115 cur_read += read;
2116 if (!read ||
2117 (cs->flags & CS_FL_WANT_ROOM) ||
2118 (--read_poll <= 0) ||
2119 (read < max && read >= global.tune.recv_enough))
2120 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002121 }
2122
Christopher Faulet61cc8522020-04-20 14:54:42 +02002123 end_recv:
2124 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2125 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2126 /* Report network errors only if we got no other data. Otherwise
2127 * we'll let the upper layers decide whether the response is OK
2128 * or not. It is very common that an RST sent by the server is
2129 * reported as an error just after the last data chunk.
2130 */
2131 goto stop;
2132 }
2133 if (!cur_read) {
2134 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2135 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2136 goto wait_more_data;
2137 }
2138 if (is_empty) {
Christopher Faulet1941bab2020-05-05 07:55:50 +02002139 int status;
2140
Christopher Faulet61cc8522020-04-20 14:54:42 +02002141 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2142 tcpcheck_get_step_id(check, rule));
2143 if (rule->comment)
2144 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002145
2146 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
2147 set_server_check_status(check, status, trash.area);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002148 goto stop;
2149 }
2150 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002151
2152 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002153 return ret;
2154
Christopher Faulet61cc8522020-04-20 14:54:42 +02002155 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002156 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002157 goto out;
2158
2159 wait_more_data:
2160 ret = TCPCHK_EVAL_WAIT;
2161 goto out;
2162}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002163
Christopher Faulet61cc8522020-04-20 14:54:42 +02002164/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2165 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2166 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2167 * error occurred.
2168 */
2169static 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 +02002170{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002171 struct htx *htx = htxbuf(&check->bi);
2172 struct htx_sl *sl;
2173 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002174 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002175 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletaaab0832020-05-05 15:54:22 +02002176 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
Christopher Faulet39708192020-05-05 10:47:36 +02002177 enum healthcheck_status status = HCHK_STATUS_L7RSP;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002178 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002179 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002180
Christopher Faulet61cc8522020-04-20 14:54:42 +02002181 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002182
Christopher Faulet61cc8522020-04-20 14:54:42 +02002183 if (htx->flags & HTX_FL_PARSING_ERROR) {
2184 status = HCHK_STATUS_L7RSP;
2185 goto error;
2186 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002187
Christopher Faulet61cc8522020-04-20 14:54:42 +02002188 if (htx_is_empty(htx)) {
2189 if (last_read) {
2190 status = HCHK_STATUS_L7RSP;
2191 goto error;
2192 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002193 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002194 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002195
Christopher Faulet61cc8522020-04-20 14:54:42 +02002196 sl = http_get_stline(htx);
2197 check->code = sl->info.res.status;
2198
2199 if (check->server &&
2200 (check->server->proxy->options & PR_O_DISABLE404) &&
2201 (check->server->next_state != SRV_ST_STOPPED) &&
2202 (check->code == 404)) {
2203 /* 404 may be accepted as "stopping" only if the server was up */
2204 goto out;
2205 }
2206
2207 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2208 /* Make GCC happy ; initialize match to a failure state. */
2209 match = inverse;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002210 status = expect->err_status;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002211
2212 switch (expect->type) {
2213 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002214 match = 0;
2215 for (i = 0; i < expect->codes.num; i++) {
2216 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2217 sl->info.res.status <= expect->codes.codes[i][1]) {
2218 match = 1;
2219 break;
2220 }
2221 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002222
2223 /* Set status and description in case of error */
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002224 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002225 if (LIST_ISEMPTY(&expect->onerror_fmt))
2226 desc = htx_sl_res_reason(sl);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002227 break;
Christopher Faulet67a23452020-05-05 18:10:01 +02002228 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
Christopher Faulet61cc8522020-04-20 14:54:42 +02002229 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2230
2231 /* Set status and description in case of error */
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002232 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002233 if (LIST_ISEMPTY(&expect->onerror_fmt))
2234 desc = htx_sl_res_reason(sl);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002235 break;
2236
Christopher Faulet39708192020-05-05 10:47:36 +02002237 case TCPCHK_EXPECT_HTTP_HEADER: {
2238 struct http_hdr_ctx ctx;
2239 struct ist npat, vpat, value;
2240 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
2241
2242 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
2243 nbuf = alloc_trash_chunk();
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002244 if (!nbuf) {
2245 status = HCHK_STATUS_L7RSP;
2246 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet39708192020-05-05 10:47:36 +02002247 goto error;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002248 }
Christopher Faulet39708192020-05-05 10:47:36 +02002249 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002250 if (!b_data(nbuf)) {
2251 status = HCHK_STATUS_L7RSP;
2252 desc = ist("log-format string evaluated to an empty string");
2253 goto error;
2254 }
Christopher Faulet39708192020-05-05 10:47:36 +02002255 npat = ist2(b_orig(nbuf), b_data(nbuf));
2256 }
2257 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
2258 npat = expect->hdr.name;
2259
2260 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
2261 vbuf = alloc_trash_chunk();
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002262 if (!vbuf) {
2263 status = HCHK_STATUS_L7RSP;
2264 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet39708192020-05-05 10:47:36 +02002265 goto error;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002266 }
Christopher Faulet39708192020-05-05 10:47:36 +02002267 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002268 if (!b_data(vbuf)) {
2269 status = HCHK_STATUS_L7RSP;
2270 desc = ist("log-format string evaluated to an empty string");
2271 goto error;
2272 }
Christopher Faulet39708192020-05-05 10:47:36 +02002273 vpat = ist2(b_orig(vbuf), b_data(vbuf));
2274 }
2275 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
2276 vpat = expect->hdr.value;
2277
2278 match = 0;
2279 ctx.blk = NULL;
2280 while (1) {
2281 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
2282 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
2283 if (!http_find_str_header(htx, npat, &ctx, full))
2284 goto end_of_match;
2285 break;
2286 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
2287 if (!http_find_pfx_header(htx, npat, &ctx, full))
2288 goto end_of_match;
2289 break;
2290 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
2291 if (!http_find_sfx_header(htx, npat, &ctx, full))
2292 goto end_of_match;
2293 break;
2294 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
2295 if (!http_find_sub_header(htx, npat, &ctx, full))
2296 goto end_of_match;
2297 break;
2298 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
2299 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
2300 goto end_of_match;
2301 break;
Christopher Faulet083eff32020-05-07 15:41:39 +02002302 default:
2303 /* should never happen */
2304 goto end_of_match;
Christopher Faulet39708192020-05-05 10:47:36 +02002305 }
2306
Christopher Faulet083eff32020-05-07 15:41:39 +02002307 /* A header has matched the name pattern, let's test its
2308 * value now (always defined from there). If there is no
2309 * value pattern, it is a good match.
2310 */
2311
Christopher Faulet39708192020-05-05 10:47:36 +02002312 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
2313 match = 1;
2314 goto end_of_match;
2315 }
2316
2317 value = ctx.value;
2318 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
2319 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
2320 if (isteq(value, vpat)) {
2321 match = 1;
2322 goto end_of_match;
2323 }
2324 break;
2325 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
2326 if (istlen(value) < istlen(vpat))
2327 break;
2328 value = ist2(istptr(value), istlen(vpat));
2329 if (isteq(value, vpat)) {
2330 match = 1;
2331 goto end_of_match;
2332 }
2333 break;
2334 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
2335 if (istlen(value) < istlen(vpat))
2336 break;
2337 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
2338 if (isteq(value, vpat)) {
2339 match = 1;
2340 goto end_of_match;
2341 }
2342 break;
2343 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
2344 if (isttest(istist(value, vpat))) {
2345 match = 1;
2346 goto end_of_match;
2347 }
2348 break;
2349 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
2350 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
2351 match = 1;
2352 goto end_of_match;
2353 }
2354 break;
2355 }
2356 }
2357
2358 end_of_match:
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002359 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
Christopher Faulet39708192020-05-05 10:47:36 +02002360 if (LIST_ISEMPTY(&expect->onerror_fmt))
2361 desc = htx_sl_res_reason(sl);
2362 break;
2363 }
2364
Christopher Faulet61cc8522020-04-20 14:54:42 +02002365 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Faulet67a23452020-05-05 18:10:01 +02002366 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
Christopher Fauletaaab0832020-05-05 15:54:22 +02002367 case TCPCHK_EXPECT_HTTP_BODY_LF:
2368 match = 0;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002369 chunk_reset(&trash);
2370 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2371 enum htx_blk_type type = htx_get_blk_type(blk);
2372
2373 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2374 break;
2375 if (type == HTX_BLK_DATA) {
2376 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2377 break;
2378 }
2379 }
2380
2381 if (!b_data(&trash)) {
2382 if (!last_read)
2383 goto wait_more_data;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002384 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002385 if (LIST_ISEMPTY(&expect->onerror_fmt))
2386 desc = ist("HTTP content check could not find a response body");
Christopher Faulet61cc8522020-04-20 14:54:42 +02002387 goto error;
2388 }
2389
Christopher Fauletaaab0832020-05-05 15:54:22 +02002390 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
2391 tmp = alloc_trash_chunk();
2392 if (!tmp) {
2393 status = HCHK_STATUS_L7RSP;
2394 desc = ist("Failed to allocate buffer to eval log-format string");
2395 goto error;
2396 }
2397 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2398 if (!b_data(tmp)) {
2399 status = HCHK_STATUS_L7RSP;
2400 desc = ist("log-format string evaluated to an empty string");
2401 goto error;
2402 }
2403 }
2404
Christopher Faulet61cc8522020-04-20 14:54:42 +02002405 if (!last_read &&
2406 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
Christopher Fauletaaab0832020-05-05 15:54:22 +02002407 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
Christopher Faulet61cc8522020-04-20 14:54:42 +02002408 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2409 ret = TCPCHK_EVAL_WAIT;
2410 goto out;
2411 }
2412
2413 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002414 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002415 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
2416 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002417 else
2418 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2419
2420 /* Set status and description in case of error */
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002421 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002422 if (LIST_ISEMPTY(&expect->onerror_fmt))
2423 desc = (inverse
2424 ? ist("HTTP check matched unwanted content")
2425 : ist("HTTP content check did not match"));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002426 break;
2427
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002428
Christopher Faulet61cc8522020-04-20 14:54:42 +02002429 default:
2430 /* should never happen */
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002431 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002432 goto error;
2433 }
2434
Christopher Faulet61cc8522020-04-20 14:54:42 +02002435 /* Wait for more data on mismatch only if no minimum is defined (-1),
2436 * otherwise the absence of match is already conclusive.
2437 */
2438 if (!match && !last_read && (expect->min_recv == -1)) {
2439 ret = TCPCHK_EVAL_WAIT;
2440 goto out;
2441 }
2442
2443 if (!(match ^ inverse))
2444 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002445
2446 out:
Christopher Fauletaaab0832020-05-05 15:54:22 +02002447 free_trash_chunk(tmp);
Christopher Faulet39708192020-05-05 10:47:36 +02002448 free_trash_chunk(nbuf);
2449 free_trash_chunk(vbuf);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002450 free_trash_chunk(msg);
2451 return ret;
2452
2453 error:
2454 ret = TCPCHK_EVAL_STOP;
2455 msg = alloc_trash_chunk();
2456 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002457 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002458 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2459 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002460
2461 wait_more_data:
2462 ret = TCPCHK_EVAL_WAIT;
2463 goto out;
2464}
2465
Christopher Faulet61cc8522020-04-20 14:54:42 +02002466/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2467 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2468 * if an error occurred.
2469 */
2470static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2471{
2472 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2473 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletaaab0832020-05-05 15:54:22 +02002474 struct buffer *msg = NULL, *tmp = NULL;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002475 struct ist desc = IST_NULL;
Christopher Faulet1941bab2020-05-05 07:55:50 +02002476 enum healthcheck_status status;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002477 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002478
Christopher Faulet61cc8522020-04-20 14:54:42 +02002479 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002480
Christopher Faulet61cc8522020-04-20 14:54:42 +02002481 /* The current expect might need more data than the previous one, check again
2482 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002483 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002484 if (!last_read) {
2485 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2486 (b_data(&check->bi) < istlen(expect->data))) {
2487 ret = TCPCHK_EVAL_WAIT;
2488 goto out;
2489 }
2490 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2491 ret = TCPCHK_EVAL_WAIT;
2492 goto out;
2493 }
2494 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002495
Christopher Faulet61cc8522020-04-20 14:54:42 +02002496 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2497 /* Make GCC happy ; initialize match to a failure state. */
2498 match = inverse;
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002499 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002500
Christopher Faulet61cc8522020-04-20 14:54:42 +02002501 switch (expect->type) {
2502 case TCPCHK_EXPECT_STRING:
2503 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002504 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 +02002505 break;
Christopher Faulet67a23452020-05-05 18:10:01 +02002506 case TCPCHK_EXPECT_STRING_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002507 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 +02002508 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002509
Christopher Faulet67a23452020-05-05 18:10:01 +02002510 case TCPCHK_EXPECT_BINARY_REGEX:
Christopher Faulet61cc8522020-04-20 14:54:42 +02002511 chunk_reset(&trash);
2512 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002513 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002514 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +02002515
2516 case TCPCHK_EXPECT_STRING_LF:
2517 case TCPCHK_EXPECT_BINARY_LF:
2518 match = 0;
2519 tmp = alloc_trash_chunk();
2520 if (!tmp) {
2521 status = HCHK_STATUS_L7RSP;
2522 desc = ist("Failed to allocate buffer to eval format string");
2523 goto error;
2524 }
2525 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2526 if (!b_data(tmp)) {
2527 status = HCHK_STATUS_L7RSP;
2528 desc = ist("log-format string evaluated to an empty string");
2529 goto error;
2530 }
2531 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2532 int len = tmp->data;
2533 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2534 status = HCHK_STATUS_L7RSP;
2535 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
2536 goto error;
2537 }
2538 tmp->data = len;
2539 }
2540 if (b_data(&check->bi) < tmp->data) {
2541 if (!last_read) {
2542 ret = TCPCHK_EVAL_WAIT;
2543 goto out;
2544 }
2545 break;
2546 }
2547 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2548 break;
2549
Christopher Faulet61cc8522020-04-20 14:54:42 +02002550 case TCPCHK_EXPECT_CUSTOM:
2551 if (expect->custom)
2552 ret = expect->custom(check, rule, last_read);
2553 goto out;
2554 default:
2555 /* Should never happen. */
2556 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002557 goto out;
2558 }
2559
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002560
Christopher Faulet61cc8522020-04-20 14:54:42 +02002561 /* Wait for more data on mismatch only if no minimum is defined (-1),
2562 * otherwise the absence of match is already conclusive.
2563 */
2564 if (!match && !last_read && (expect->min_recv == -1)) {
2565 ret = TCPCHK_EVAL_WAIT;
2566 goto out;
2567 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002568
Christopher Faulet61cc8522020-04-20 14:54:42 +02002569 /* Result as expected, next rule. */
2570 if (match ^ inverse)
2571 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002572
Christopher Fauletaaab0832020-05-05 15:54:22 +02002573 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02002574 /* From this point on, we matched something we did not want, this is an error state. */
2575 ret = TCPCHK_EVAL_STOP;
2576 msg = alloc_trash_chunk();
2577 if (msg)
Christopher Faulet0d6909b2020-05-05 15:50:37 +02002578 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002579 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002580 free_trash_chunk(msg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002581
Christopher Faulet61cc8522020-04-20 14:54:42 +02002582 out:
Christopher Fauletaaab0832020-05-05 15:54:22 +02002583 free_trash_chunk(tmp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002584 return ret;
2585}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002586
Christopher Faulet61cc8522020-04-20 14:54:42 +02002587/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05002588 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
Christopher Faulet61cc8522020-04-20 14:54:42 +02002589 * waits.
2590 */
2591static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2592{
2593 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2594 struct act_rule *act_rule;
2595 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002596
Christopher Faulet61cc8522020-04-20 14:54:42 +02002597 act_rule =rule->action_kw.rule;
2598 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2599 if (act_ret != ACT_RET_CONT) {
2600 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2601 tcpcheck_get_step_id(check, rule));
2602 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2603 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002604 }
2605
Christopher Faulet61cc8522020-04-20 14:54:42 +02002606 return ret;
2607}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002608
Christopher Faulet61cc8522020-04-20 14:54:42 +02002609/* Executes a tcp-check ruleset. Note that this is called both from the
2610 * connection's wake() callback and from the check scheduling task. It returns
2611 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2612 * presenting the risk of an fd replacement.
2613 *
2614 * Please do NOT place any return statement in this function and only leave
2615 * via the out_end_tcpcheck label after setting retcode.
2616 */
2617static int tcpcheck_main(struct check *check)
2618{
2619 struct tcpcheck_rule *rule;
2620 struct conn_stream *cs = check->cs;
2621 struct connection *conn = cs_conn(cs);
2622 int must_read = 1, last_read = 0;
2623 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625 /* here, we know that the check is complete or that it failed */
2626 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002627 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002628
Christopher Faulet61cc8522020-04-20 14:54:42 +02002629 /* 1- check for connection error, if any */
2630 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2631 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002632
Christopher Faulet61cc8522020-04-20 14:54:42 +02002633 /* 2- check if we are waiting for the connection establishment. It only
2634 * happens during TCPCHK_ACT_CONNECT. */
2635 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002636 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
Christopher Faulet97b2a8b2020-05-09 17:34:43 +02002637 struct tcpcheck_rule *next;
2638
2639 next = get_next_tcpcheck_rule(check->tcpcheck_rules, check->current_step);
2640 if (next && next->action == TCPCHK_ACT_SEND) {
Christopher Fauleta2fb0c32020-05-09 17:27:43 +02002641 if (!(check->wait_list.events & SUB_RETRY_SEND))
2642 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2643 }
Christopher Faulet97b2a8b2020-05-09 17:34:43 +02002644 else {
Christopher Fauleta2fb0c32020-05-09 17:27:43 +02002645 if (!(check->wait_list.events & SUB_RETRY_RECV))
2646 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2647 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002648 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002649 }
Christopher Faulet97b2a8b2020-05-09 17:34:43 +02002650 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002651 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002652
2653 /* 3- check for pending outgoing data. It only happens during
2654 * TCPCHK_ACT_SEND. */
2655 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2656 if (conn && b_data(&check->bo)) {
Christopher Faulet07321342020-05-09 17:37:43 +02002657 /* We're already waiting to be able to send, give up */
2658 if (check->wait_list.events & SUB_RETRY_SEND)
2659 goto out;
2660
Christopher Faulet6d471212020-04-22 11:09:25 +02002661 ret = conn->mux->snd_buf(cs, &check->bo,
2662 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002663 if (ret <= 0) {
2664 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2665 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002666 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002667 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002668 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2669 goto out;
2670 }
2671 }
2672 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002673 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002674
Christopher Faulet61cc8522020-04-20 14:54:42 +02002675 /* 4- check if a rule must be resume. It happens if check->current_step
2676 * is defined. */
2677 else if (check->current_step)
2678 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002679
Christopher Faulet61cc8522020-04-20 14:54:42 +02002680 /* 5- It is the first evaluation. We must create a session and preset
2681 * tcp-check variables */
2682 else {
2683 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002684
Christopher Faulet61cc8522020-04-20 14:54:42 +02002685 /* First evaluation, create a session */
2686 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2687 if (!check->sess) {
2688 chunk_printf(&trash, "TCPCHK error allocating check session");
2689 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2690 goto out_end_tcpcheck;
2691 }
2692 vars_init(&check->vars, SCOPE_CHECK);
2693 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002694
Christopher Faulet61cc8522020-04-20 14:54:42 +02002695 /* Preset tcp-check variables */
2696 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2697 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002698
Christopher Faulet61cc8522020-04-20 14:54:42 +02002699 memset(&smp, 0, sizeof(smp));
2700 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2701 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002702 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002703 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002704 }
2705
Christopher Faulet61cc8522020-04-20 14:54:42 +02002706 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002707
Christopher Faulet61cc8522020-04-20 14:54:42 +02002708 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2709 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002710
Christopher Faulet61cc8522020-04-20 14:54:42 +02002711 check->code = 0;
2712 switch (rule->action) {
2713 case TCPCHK_ACT_CONNECT:
2714 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002715
Christopher Faulet61cc8522020-04-20 14:54:42 +02002716 /* close but not release yet previous connection */
2717 if (check->cs) {
2718 cs_close(check->cs);
2719 retcode = -1; /* do not reuse the fd in the caller! */
2720 }
2721 eval_ret = tcpcheck_eval_connect(check, rule);
2722 must_read = 1; last_read = 0;
2723 break;
2724 case TCPCHK_ACT_SEND:
2725 check->current_step = rule;
2726 eval_ret = tcpcheck_eval_send(check, rule);
2727 must_read = 1;
2728 break;
2729 case TCPCHK_ACT_EXPECT:
2730 check->current_step = rule;
2731 if (must_read) {
2732 if (check->proxy->timeout.check)
2733 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002734
Christopher Faulet61cc8522020-04-20 14:54:42 +02002735 eval_ret = tcpcheck_eval_recv(check, rule);
2736 if (eval_ret == TCPCHK_EVAL_STOP)
2737 goto out_end_tcpcheck;
2738 else if (eval_ret == TCPCHK_EVAL_WAIT)
2739 goto out;
2740 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2741 must_read = 0;
2742 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002743
Christopher Faulet61cc8522020-04-20 14:54:42 +02002744 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2745 ? tcpcheck_eval_expect_http(check, rule, last_read)
2746 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002747
Christopher Faulet61cc8522020-04-20 14:54:42 +02002748 if (eval_ret == TCPCHK_EVAL_WAIT) {
2749 check->current_step = rule->expect.head;
Christopher Fauleta2fb0c32020-05-09 17:27:43 +02002750 if (!(check->wait_list.events & SUB_RETRY_RECV))
2751 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002752 }
2753 break;
2754 case TCPCHK_ACT_ACTION_KW:
2755 /* Don't update the current step */
2756 eval_ret = tcpcheck_eval_action_kw(check, rule);
2757 break;
2758 default:
2759 /* Otherwise, just go to the next one and don't update
2760 * the current step
2761 */
2762 eval_ret = TCPCHK_EVAL_CONTINUE;
2763 break;
2764 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002765
Christopher Faulet61cc8522020-04-20 14:54:42 +02002766 switch (eval_ret) {
2767 case TCPCHK_EVAL_CONTINUE:
2768 break;
2769 case TCPCHK_EVAL_WAIT:
2770 goto out;
2771 case TCPCHK_EVAL_STOP:
2772 goto out_end_tcpcheck;
2773 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002774 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002775
Christopher Faulet61cc8522020-04-20 14:54:42 +02002776 /* All rules was evaluated */
2777 if (check->current_step) {
2778 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002779
Christopher Faulet61cc8522020-04-20 14:54:42 +02002780 if (rule->action == TCPCHK_ACT_EXPECT) {
2781 struct buffer *msg;
Christopher Faulet1941bab2020-05-05 07:55:50 +02002782 enum healthcheck_status status;
Willy Tarreau00149122017-10-04 18:05:01 +02002783
Christopher Faulet61cc8522020-04-20 14:54:42 +02002784 if (check->server &&
2785 (check->server->proxy->options & PR_O_DISABLE404) &&
2786 (check->server->next_state != SRV_ST_STOPPED) &&
2787 (check->code == 404)) {
2788 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2789 goto out_end_tcpcheck;
2790 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002791
Christopher Faulet61cc8522020-04-20 14:54:42 +02002792 msg = alloc_trash_chunk();
2793 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002794 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet1941bab2020-05-05 07:55:50 +02002795 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2796 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002797 free_trash_chunk(msg);
2798 }
2799 else if (rule->action == TCPCHK_ACT_CONNECT) {
2800 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002801 enum healthcheck_status status = HCHK_STATUS_L4OK;
2802#ifdef USE_OPENSSL
2803 if (conn && ssl_sock_is_ssl(conn))
2804 status = HCHK_STATUS_L6OK;
2805#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002806 set_server_check_status(check, status, msg);
2807 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002808 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002809 else
2810 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002811
Christopher Faulet61cc8522020-04-20 14:54:42 +02002812 out_end_tcpcheck:
2813 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2814 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002815
Christopher Faulet61cc8522020-04-20 14:54:42 +02002816 out:
2817 return retcode;
2818}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002819
Christopher Faulet14cd3162020-04-16 14:50:06 +02002820
Christopher Faulet61cc8522020-04-20 14:54:42 +02002821/**************************************************************************/
2822/************** Health-checks based on an external process ****************/
2823/**************************************************************************/
2824static struct list pid_list = LIST_HEAD_INIT(pid_list);
2825static struct pool_head *pool_head_pid_list;
2826__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002827
Christopher Faulet61cc8522020-04-20 14:54:42 +02002828struct extcheck_env {
2829 char *name; /* environment variable name */
2830 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2831};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002832
Christopher Faulet61cc8522020-04-20 14:54:42 +02002833/* environment variables memory requirement for different types of data */
2834#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2835 * such environment variables are not updatable. */
2836#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2837#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2838#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002839
Christopher Faulet61cc8522020-04-20 14:54:42 +02002840/* external checks environment variables */
2841enum {
2842 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002843
Christopher Faulet61cc8522020-04-20 14:54:42 +02002844 /* Proxy specific environment variables */
2845 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2846 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2847 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2848 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002849
Christopher Faulet61cc8522020-04-20 14:54:42 +02002850 /* Server specific environment variables */
2851 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2852 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2853 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2854 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2855 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2856 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002857
Christopher Faulet61cc8522020-04-20 14:54:42 +02002858 EXTCHK_SIZE
2859};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002860
Christopher Faulet61cc8522020-04-20 14:54:42 +02002861const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2862 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2863 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2864 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2865 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2866 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2867 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2868 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2869 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2870 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2871 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2872 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2873};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002874
Christopher Faulet61cc8522020-04-20 14:54:42 +02002875void block_sigchld(void)
2876{
2877 sigset_t set;
2878 sigemptyset(&set);
2879 sigaddset(&set, SIGCHLD);
2880 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2881}
Willy Tarreaube373152018-09-06 11:45:30 +02002882
Christopher Faulet61cc8522020-04-20 14:54:42 +02002883void unblock_sigchld(void)
2884{
2885 sigset_t set;
2886 sigemptyset(&set);
2887 sigaddset(&set, SIGCHLD);
2888 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002889}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002890
Christopher Faulet61cc8522020-04-20 14:54:42 +02002891static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002892{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002893 struct pid_list *elem;
2894 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002895
Christopher Faulet61cc8522020-04-20 14:54:42 +02002896 elem = pool_alloc(pool_head_pid_list);
2897 if (!elem)
2898 return NULL;
2899 elem->pid = pid;
2900 elem->t = t;
2901 elem->exited = 0;
2902 check->curpid = elem;
2903 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002904
Christopher Faulet61cc8522020-04-20 14:54:42 +02002905 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2906 LIST_ADD(&pid_list, &elem->list);
2907 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002908
Christopher Faulet61cc8522020-04-20 14:54:42 +02002909 return elem;
2910}
Christopher Faulete5870d82020-04-15 11:32:03 +02002911
Christopher Faulet61cc8522020-04-20 14:54:42 +02002912static void pid_list_del(struct pid_list *elem)
2913{
2914 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002915
Christopher Faulet61cc8522020-04-20 14:54:42 +02002916 if (!elem)
2917 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002918
Christopher Faulet61cc8522020-04-20 14:54:42 +02002919 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2920 LIST_DEL(&elem->list);
2921 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002922
Christopher Faulet61cc8522020-04-20 14:54:42 +02002923 if (!elem->exited)
2924 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002925
Christopher Faulet61cc8522020-04-20 14:54:42 +02002926 check = elem->t->context;
2927 check->curpid = NULL;
2928 pool_free(pool_head_pid_list, elem);
2929}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002930
Christopher Faulet61cc8522020-04-20 14:54:42 +02002931/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2932static void pid_list_expire(pid_t pid, int status)
2933{
2934 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002935
Christopher Faulet61cc8522020-04-20 14:54:42 +02002936 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2937 list_for_each_entry(elem, &pid_list, list) {
2938 if (elem->pid == pid) {
2939 elem->t->expire = now_ms;
2940 elem->status = status;
2941 elem->exited = 1;
2942 task_wakeup(elem->t, TASK_WOKEN_IO);
2943 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002944 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002945 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002946 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2947}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002948
Christopher Faulet61cc8522020-04-20 14:54:42 +02002949static void sigchld_handler(struct sig_handler *sh)
2950{
2951 pid_t pid;
2952 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002953
Christopher Faulet61cc8522020-04-20 14:54:42 +02002954 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2955 pid_list_expire(pid, status);
2956}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002957
Christopher Faulet61cc8522020-04-20 14:54:42 +02002958static int init_pid_list(void)
2959{
2960 if (pool_head_pid_list != NULL)
2961 /* Nothing to do */
2962 return 0;
2963
2964 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2965 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2966 strerror(errno));
2967 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002968 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002969
Christopher Faulet61cc8522020-04-20 14:54:42 +02002970 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2971 if (pool_head_pid_list == NULL) {
2972 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2973 strerror(errno));
2974 return 1;
2975 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002976
Christopher Faulet61cc8522020-04-20 14:54:42 +02002977 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002978}
2979
Christopher Faulet61cc8522020-04-20 14:54:42 +02002980/* helper macro to set an environment variable and jump to a specific label on failure. */
2981#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002982
Christopher Faulet61cc8522020-04-20 14:54:42 +02002983/*
2984 * helper function to allocate enough memory to store an environment variable.
2985 * It will also check that the environment variable is updatable, and silently
2986 * fail if not.
2987 */
2988static int extchk_setenv(struct check *check, int idx, const char *value)
2989{
2990 int len, ret;
2991 char *envname;
2992 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002993
Christopher Faulet61cc8522020-04-20 14:54:42 +02002994 if (idx < 0 || idx >= EXTCHK_SIZE) {
2995 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2996 return 1;
2997 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002998
Christopher Faulet61cc8522020-04-20 14:54:42 +02002999 envname = extcheck_envs[idx].name;
3000 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02003001
Christopher Faulet61cc8522020-04-20 14:54:42 +02003002 /* Check if the environment variable is already set, and silently reject
3003 * the update if this one is not updatable. */
3004 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
3005 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02003006
Christopher Faulet61cc8522020-04-20 14:54:42 +02003007 /* Instead of sending NOT_USED, sending an empty value is preferable */
3008 if (strcmp(value, "NOT_USED") == 0) {
3009 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02003010 }
3011
Christopher Faulet61cc8522020-04-20 14:54:42 +02003012 len = strlen(envname) + 1;
3013 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
3014 len += strlen(value);
3015 else
3016 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02003017
Christopher Faulet61cc8522020-04-20 14:54:42 +02003018 if (!check->envp[idx])
3019 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02003020
Christopher Faulet61cc8522020-04-20 14:54:42 +02003021 if (!check->envp[idx]) {
3022 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
3023 return 1;
3024 }
3025 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
3026 if (ret < 0) {
3027 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
3028 return 1;
3029 }
3030 else if (ret > len) {
3031 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
3032 return 1;
3033 }
3034 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02003035}
3036
Christopher Faulet61cc8522020-04-20 14:54:42 +02003037static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02003038{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003039 struct server *s = check->server;
3040 struct proxy *px = s->proxy;
3041 struct listener *listener = NULL, *l;
3042 int i;
3043 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
3044 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02003045
Christopher Faulet61cc8522020-04-20 14:54:42 +02003046 list_for_each_entry(l, &px->conf.listeners, by_fe)
3047 /* Use the first INET, INET6 or UNIX listener */
3048 if (l->addr.ss_family == AF_INET ||
3049 l->addr.ss_family == AF_INET6 ||
3050 l->addr.ss_family == AF_UNIX) {
3051 listener = l;
3052 break;
3053 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003054
Christopher Faulet61cc8522020-04-20 14:54:42 +02003055 check->curpid = NULL;
3056 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
3057 if (!check->envp) {
3058 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
3059 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02003060 }
3061
Christopher Faulet61cc8522020-04-20 14:54:42 +02003062 check->argv = calloc(6, sizeof(char *));
3063 if (!check->argv) {
3064 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
3065 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02003066 }
3067
Christopher Faulet61cc8522020-04-20 14:54:42 +02003068 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02003069
Christopher Faulet61cc8522020-04-20 14:54:42 +02003070 if (!listener) {
3071 check->argv[1] = strdup("NOT_USED");
3072 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02003073 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003074 else if (listener->addr.ss_family == AF_INET ||
3075 listener->addr.ss_family == AF_INET6) {
3076 addr_to_str(&listener->addr, buf, sizeof(buf));
3077 check->argv[1] = strdup(buf);
3078 port_to_str(&listener->addr, buf, sizeof(buf));
3079 check->argv[2] = strdup(buf);
3080 }
3081 else if (listener->addr.ss_family == AF_UNIX) {
3082 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02003083
Christopher Faulet61cc8522020-04-20 14:54:42 +02003084 un = (struct sockaddr_un *)&listener->addr;
3085 check->argv[1] = strdup(un->sun_path);
3086 check->argv[2] = strdup("NOT_USED");
3087 }
3088 else {
3089 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
3090 goto err;
3091 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02003092
Christopher Faulet61cc8522020-04-20 14:54:42 +02003093 if (!check->argv[1] || !check->argv[2]) {
3094 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
3095 goto err;
3096 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02003097
Christopher Faulet61cc8522020-04-20 14:54:42 +02003098 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
3099 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
3100 if (!check->argv[3] || !check->argv[4]) {
3101 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
3102 goto err;
3103 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02003104
Christopher Faulet61cc8522020-04-20 14:54:42 +02003105 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
3106 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
3107 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02003108
Christopher Faulet61cc8522020-04-20 14:54:42 +02003109 for (i = 0; i < 5; i++) {
3110 if (!check->argv[i]) {
3111 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
3112 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02003113 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003114 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003115
Christopher Faulet61cc8522020-04-20 14:54:42 +02003116 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
3117 /* Add proxy environment variables */
3118 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
3119 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
3120 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
3121 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
3122 /* Add server environment variables */
3123 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
3124 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
3125 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
3126 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
3127 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
3128 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02003129
Christopher Faulet61cc8522020-04-20 14:54:42 +02003130 /* Ensure that we don't leave any hole in check->envp */
3131 for (i = 0; i < EXTCHK_SIZE; i++)
3132 if (!check->envp[i])
3133 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02003134
Christopher Faulet61cc8522020-04-20 14:54:42 +02003135 return 1;
3136err:
3137 if (check->envp) {
3138 for (i = 0; i < EXTCHK_SIZE; i++)
3139 free(check->envp[i]);
3140 free(check->envp);
3141 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02003142 }
3143
Christopher Faulet61cc8522020-04-20 14:54:42 +02003144 if (check->argv) {
3145 for (i = 1; i < 5; i++)
3146 free(check->argv[i]);
3147 free(check->argv);
3148 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02003149 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003150 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003151}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003152
Christopher Faulet61cc8522020-04-20 14:54:42 +02003153/*
3154 * establish a server health-check that makes use of a process.
3155 *
3156 * It can return one of :
3157 * - SF_ERR_NONE if everything's OK
3158 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
3159 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
3160 *
3161 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003162 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02003163static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003164{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003165 char buf[256];
3166 struct check *check = t->context;
3167 struct server *s = check->server;
3168 struct proxy *px = s->proxy;
3169 int status;
3170 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003171
Christopher Faulet61cc8522020-04-20 14:54:42 +02003172 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003173
Christopher Faulet61cc8522020-04-20 14:54:42 +02003174 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003175
Christopher Faulet61cc8522020-04-20 14:54:42 +02003176 pid = fork();
3177 if (pid < 0) {
3178 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
3179 (global.tune.options & GTUNE_INSECURE_FORK) ?
3180 "" : " (likely caused by missing 'insecure-fork-wanted')",
3181 strerror(errno));
3182 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003183 goto out;
3184 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003185 if (pid == 0) {
3186 /* Child */
3187 extern char **environ;
3188 struct rlimit limit;
3189 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003190
Christopher Faulet61cc8522020-04-20 14:54:42 +02003191 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
3192 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003193
Christopher Faulet61cc8522020-04-20 14:54:42 +02003194 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003195
Christopher Faulet61cc8522020-04-20 14:54:42 +02003196 /* restore the initial FD limits */
3197 limit.rlim_cur = rlim_fd_cur_at_boot;
3198 limit.rlim_max = rlim_fd_max_at_boot;
3199 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
3200 getrlimit(RLIMIT_NOFILE, &limit);
3201 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
3202 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
3203 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
3204 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003205
Christopher Faulet61cc8522020-04-20 14:54:42 +02003206 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003207
Christopher Faulet61cc8522020-04-20 14:54:42 +02003208 /* Update some environment variables and command args: curconn, server addr and server port */
3209 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003210
Christopher Faulet61cc8522020-04-20 14:54:42 +02003211 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
3212 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003213
Christopher Faulet61cc8522020-04-20 14:54:42 +02003214 *check->argv[4] = 0;
3215 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
3216 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
3217 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003218
Christopher Faulet61cc8522020-04-20 14:54:42 +02003219 haproxy_unblock_signals();
3220 execvp(px->check_command, check->argv);
3221 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
3222 strerror(errno));
3223 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003224 }
3225
Christopher Faulet61cc8522020-04-20 14:54:42 +02003226 /* Parent */
3227 if (check->result == CHK_RES_UNKNOWN) {
3228 if (pid_list_add(pid, t) != NULL) {
3229 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3230
3231 if (px->timeout.check && px->timeout.connect) {
3232 int t_con = tick_add(now_ms, px->timeout.connect);
3233 t->expire = tick_first(t->expire, t_con);
3234 }
3235 status = SF_ERR_NONE;
3236 goto out;
3237 }
3238 else {
3239 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
3240 }
3241 kill(pid, SIGTERM); /* process creation error */
3242 }
3243 else
3244 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
3245
3246out:
3247 unblock_sigchld();
3248 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003249}
3250
Christopher Faulet61cc8522020-04-20 14:54:42 +02003251/*
3252 * manages a server health-check that uses an external process. Returns
3253 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003254 *
3255 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02003256 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003257 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02003258static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003259{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003260 struct check *check = context;
3261 struct server *s = check->server;
3262 int rv;
3263 int ret;
3264 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003265
Christopher Faulet61cc8522020-04-20 14:54:42 +02003266 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3267 if (!(check->state & CHK_ST_INPROGRESS)) {
3268 /* no check currently running */
3269 if (!expired) /* woke up too early */
3270 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003271
Christopher Faulet61cc8522020-04-20 14:54:42 +02003272 /* we don't send any health-checks when the proxy is
3273 * stopped, the server should not be checked or the check
3274 * is disabled.
3275 */
3276 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3277 s->proxy->state == PR_STSTOPPED)
3278 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003279
Christopher Faulet61cc8522020-04-20 14:54:42 +02003280 /* we'll initiate a new check */
3281 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003282
Christopher Faulet61cc8522020-04-20 14:54:42 +02003283 check->state |= CHK_ST_INPROGRESS;
3284
3285 ret = connect_proc_chk(t);
3286 if (ret == SF_ERR_NONE) {
3287 /* the process was forked, we allow up to min(inter,
3288 * timeout.connect) for it to report its status, but
3289 * only when timeout.check is set as it may be to short
3290 * for a full check otherwise.
3291 */
3292 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3293
3294 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3295 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3296 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003297 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003298 task_set_affinity(t, tid_bit);
3299 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003300 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003301
Christopher Faulet61cc8522020-04-20 14:54:42 +02003302 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003303
Christopher Faulet61cc8522020-04-20 14:54:42 +02003304 check->state &= ~CHK_ST_INPROGRESS;
3305 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003306
Christopher Faulet61cc8522020-04-20 14:54:42 +02003307 /* we allow up to min(inter, timeout.connect) for a connection
3308 * to establish but only when timeout.check is set
3309 * as it may be to short for a full check otherwise
3310 */
3311 while (tick_is_expired(t->expire, now_ms)) {
3312 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003313
Christopher Faulet61cc8522020-04-20 14:54:42 +02003314 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3315 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003316
Christopher Faulet61cc8522020-04-20 14:54:42 +02003317 if (s->proxy->timeout.check)
3318 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003319 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003320 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003321 else {
3322 /* there was a test running.
3323 * First, let's check whether there was an uncaught error,
3324 * which can happen on connect timeout or error.
3325 */
3326 if (check->result == CHK_RES_UNKNOWN) {
3327 /* good connection is enough for pure TCP check */
3328 struct pid_list *elem = check->curpid;
3329 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003330
Christopher Faulet61cc8522020-04-20 14:54:42 +02003331 if (elem->exited) {
3332 status = elem->status; /* Save in case the process exits between use below */
3333 if (!WIFEXITED(status))
3334 check->code = -1;
3335 else
3336 check->code = WEXITSTATUS(status);
3337 if (!WIFEXITED(status) || WEXITSTATUS(status))
3338 status = HCHK_STATUS_PROCERR;
3339 else
3340 status = HCHK_STATUS_PROCOK;
3341 } else if (expired) {
3342 status = HCHK_STATUS_PROCTOUT;
3343 ha_warning("kill %d\n", (int)elem->pid);
3344 kill(elem->pid, SIGTERM);
3345 }
3346 set_server_check_status(check, status, NULL);
3347 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003348
Christopher Faulet61cc8522020-04-20 14:54:42 +02003349 if (check->result == CHK_RES_FAILED) {
3350 /* a failure or timeout detected */
3351 check_notify_failure(check);
3352 }
3353 else if (check->result == CHK_RES_CONDPASS) {
3354 /* check is OK but asks for stopping mode */
3355 check_notify_stopping(check);
3356 }
3357 else if (check->result == CHK_RES_PASSED) {
3358 /* a success was detected */
3359 check_notify_success(check);
3360 }
3361 task_set_affinity(t, 1);
3362 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003363
Christopher Faulet61cc8522020-04-20 14:54:42 +02003364 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003365
Christopher Faulet61cc8522020-04-20 14:54:42 +02003366 rv = 0;
3367 if (global.spread_checks > 0) {
3368 rv = srv_getinter(check) * global.spread_checks / 100;
3369 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3370 }
3371 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3372 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003373
Christopher Faulet61cc8522020-04-20 14:54:42 +02003374 reschedule:
3375 while (tick_is_expired(t->expire, now_ms))
3376 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003377
Christopher Faulet61cc8522020-04-20 14:54:42 +02003378 out_unlock:
3379 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3380 return t;
3381}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003382
Baptiste Assmann248f1172018-03-01 21:49:01 +01003383
Christopher Faulet61cc8522020-04-20 14:54:42 +02003384/**************************************************************************/
3385/***************** Health-checks based on connections *********************/
3386/**************************************************************************/
3387/* This function is used only for server health-checks. It handles connection
3388 * status updates including errors. If necessary, it wakes the check task up.
3389 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3390 * connection (eg: reconnect). It relies on tcpcheck_main().
3391 */
3392static int wake_srv_chk(struct conn_stream *cs)
3393{
3394 struct connection *conn = cs->conn;
3395 struct check *check = cs->data;
3396 struct email_alertq *q = container_of(check, typeof(*q), check);
3397 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003398
Christopher Faulet61cc8522020-04-20 14:54:42 +02003399 if (check->server)
3400 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3401 else
3402 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003403
Christopher Faulet61cc8522020-04-20 14:54:42 +02003404 /* we may have to make progress on the TCP checks */
3405 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003406
Christopher Faulet61cc8522020-04-20 14:54:42 +02003407 cs = check->cs;
3408 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003409
Christopher Faulet61cc8522020-04-20 14:54:42 +02003410 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3411 /* We may get error reports bypassing the I/O handlers, typically
3412 * the case when sending a pure TCP check which fails, then the I/O
3413 * handlers above are not called. This is completely handled by the
3414 * main processing task so let's simply wake it up. If we get here,
3415 * we expect errno to still be valid.
3416 */
3417 chk_report_conn_err(check, errno, 0);
3418 task_wakeup(check->task, TASK_WOKEN_IO);
3419 }
3420
3421 if (check->result != CHK_RES_UNKNOWN) {
3422 /* Check complete or aborted. If connection not yet closed do it
3423 * now and wake the check task up to be sure the result is
3424 * handled ASAP. */
3425 conn_sock_drain(conn);
3426 cs_close(cs);
3427 ret = -1;
3428 /* We may have been scheduled to run, and the
3429 * I/O handler expects to have a cs, so remove
3430 * the tasklet
3431 */
3432 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3433 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003434 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003435
3436 if (check->server)
3437 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003438 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003439 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003440
Christopher Faulet61cc8522020-04-20 14:54:42 +02003441 /* if a connection got replaced, we must absolutely prevent the connection
3442 * handler from touching its fd, and perform the FD polling updates ourselves
3443 */
3444 if (ret < 0)
3445 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003446
Christopher Faulet61cc8522020-04-20 14:54:42 +02003447 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003448}
3449
Christopher Faulet61cc8522020-04-20 14:54:42 +02003450/* This function checks if any I/O is wanted, and if so, attempts to do so */
3451static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003452{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003453 struct check *check = ctx;
3454 struct conn_stream *cs = check->cs;
3455 struct email_alertq *q = container_of(check, typeof(*q), check);
3456 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003457
Christopher Faulet61cc8522020-04-20 14:54:42 +02003458 if (!(check->wait_list.events & SUB_RETRY_SEND))
3459 ret = wake_srv_chk(cs);
3460 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3461 if (check->server)
3462 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3463 else
3464 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003465
Christopher Faulet61cc8522020-04-20 14:54:42 +02003466 if (unlikely(check->result == CHK_RES_FAILED)) {
3467 /* collect possible new errors */
3468 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3469 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003470
Christopher Faulet61cc8522020-04-20 14:54:42 +02003471 /* Reset the check buffer... */
3472 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003473
Christopher Faulet61cc8522020-04-20 14:54:42 +02003474 /* Close the connection... We still attempt to nicely close if,
3475 * for instance, SSL needs to send a "close notify." Later, we perform
3476 * a hard close and reset the connection if some data are pending,
3477 * otherwise we end up with many TIME_WAITs and eat all the source port
3478 * range quickly. To avoid sending RSTs all the time, we first try to
3479 * drain pending data.
3480 */
3481 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3482 * connection, to make sure cs_shutw() will not lead to a shutdown()
3483 * that would provoke TIME_WAITs.
3484 */
3485 cs_shutr(cs, CS_SHR_DRAIN);
3486 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003487
Christopher Faulet61cc8522020-04-20 14:54:42 +02003488 /* OK, let's not stay here forever */
3489 if (check->result == CHK_RES_FAILED)
3490 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003491
Christopher Faulet61cc8522020-04-20 14:54:42 +02003492 task_wakeup(t, TASK_WOKEN_IO);
3493 }
3494
3495 if (check->server)
3496 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3497 else
3498 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003499 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003500 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003501}
3502
Christopher Faulet61cc8522020-04-20 14:54:42 +02003503/* manages a server health-check that uses a connection. Returns
3504 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3505 *
3506 * Please do NOT place any return statement in this function and only leave
3507 * via the out_unlock label.
3508 */
3509static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003510{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003511 struct check *check = context;
3512 struct proxy *proxy = check->proxy;
3513 struct conn_stream *cs = check->cs;
3514 struct connection *conn = cs_conn(cs);
3515 int rv;
3516 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003517
Christopher Faulet61cc8522020-04-20 14:54:42 +02003518 if (check->server)
3519 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3520 if (!(check->state & CHK_ST_INPROGRESS)) {
3521 /* no check currently running */
3522 if (!expired) /* woke up too early */
3523 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003524
Christopher Faulet61cc8522020-04-20 14:54:42 +02003525 /* we don't send any health-checks when the proxy is
3526 * stopped, the server should not be checked or the check
3527 * is disabled.
3528 */
3529 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3530 proxy->state == PR_STSTOPPED)
3531 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003532
Christopher Faulet61cc8522020-04-20 14:54:42 +02003533 /* we'll initiate a new check */
3534 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003535
Christopher Faulet61cc8522020-04-20 14:54:42 +02003536 check->state |= CHK_ST_INPROGRESS;
3537 b_reset(&check->bi);
3538 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003539
Christopher Faulet61cc8522020-04-20 14:54:42 +02003540 task_set_affinity(t, tid_bit);
3541 cs = check->cs;
3542 conn = cs_conn(cs);
3543 if (!conn) {
3544 check->current_step = NULL;
3545 tcpcheck_main(check);
3546 goto out_unlock;
3547 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003548
Christopher Faulet61cc8522020-04-20 14:54:42 +02003549 conn->flags |= CO_FL_ERROR;
3550 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003551
Christopher Faulet61cc8522020-04-20 14:54:42 +02003552 /* here, we have seen a synchronous error, no fd was allocated */
3553 task_set_affinity(t, MAX_THREADS_MASK);
3554 if (cs) {
3555 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003556 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003557 /* We may have been scheduled to run, and the
3558 * I/O handler expects to have a cs, so remove
3559 * the tasklet
3560 */
3561 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3562 cs_destroy(cs);
3563 cs = check->cs = NULL;
3564 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003565 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003566
Christopher Faulet61cc8522020-04-20 14:54:42 +02003567 check->state &= ~CHK_ST_INPROGRESS;
3568 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003569
Christopher Faulet61cc8522020-04-20 14:54:42 +02003570 /* we allow up to min(inter, timeout.connect) for a connection
3571 * to establish but only when timeout.check is set
3572 * as it may be to short for a full check otherwise
3573 */
3574 while (tick_is_expired(t->expire, now_ms)) {
3575 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003576
Christopher Faulet61cc8522020-04-20 14:54:42 +02003577 t_con = tick_add(t->expire, proxy->timeout.connect);
3578 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3579 if (proxy->timeout.check)
3580 t->expire = tick_first(t->expire, t_con);
3581 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003582 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003583 else {
3584 /* there was a test running.
3585 * First, let's check whether there was an uncaught error,
3586 * which can happen on connect timeout or error.
3587 */
3588 if (check->result == CHK_RES_UNKNOWN) {
3589 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3590 chk_report_conn_err(check, 0, expired);
3591 }
3592 else
3593 goto out_unlock; /* timeout not reached, wait again */
3594 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003595
Christopher Faulet61cc8522020-04-20 14:54:42 +02003596 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003597
Christopher Faulet61cc8522020-04-20 14:54:42 +02003598 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003599
Christopher Faulet61cc8522020-04-20 14:54:42 +02003600 if (conn && conn->xprt) {
3601 /* The check was aborted and the connection was not yet closed.
3602 * This can happen upon timeout, or when an external event such
3603 * as a failed response coupled with "observe layer7" caused the
3604 * server state to be suddenly changed.
3605 */
3606 conn_sock_drain(conn);
3607 cs_close(cs);
3608 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003609
Christopher Faulet61cc8522020-04-20 14:54:42 +02003610 if (cs) {
3611 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003612 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003613 /* We may have been scheduled to run, and the
3614 * I/O handler expects to have a cs, so remove
3615 * the tasklet
3616 */
3617 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3618 cs_destroy(cs);
3619 cs = check->cs = NULL;
3620 conn = NULL;
3621 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003622
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003623 if (check->sess != NULL) {
3624 vars_prune(&check->vars, check->sess, NULL);
3625 session_free(check->sess);
3626 check->sess = NULL;
3627 }
3628
Christopher Faulet61cc8522020-04-20 14:54:42 +02003629 if (check->server) {
3630 if (check->result == CHK_RES_FAILED) {
3631 /* a failure or timeout detected */
3632 check_notify_failure(check);
3633 }
3634 else if (check->result == CHK_RES_CONDPASS) {
3635 /* check is OK but asks for stopping mode */
3636 check_notify_stopping(check);
3637 }
3638 else if (check->result == CHK_RES_PASSED) {
3639 /* a success was detected */
3640 check_notify_success(check);
3641 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003642 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003643 task_set_affinity(t, MAX_THREADS_MASK);
3644 check->state &= ~CHK_ST_INPROGRESS;
3645
3646 if (check->server) {
3647 rv = 0;
3648 if (global.spread_checks > 0) {
3649 rv = srv_getinter(check) * global.spread_checks / 100;
3650 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3651 }
3652 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003653 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003654 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003655
Christopher Faulet61cc8522020-04-20 14:54:42 +02003656 reschedule:
3657 while (tick_is_expired(t->expire, now_ms))
3658 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3659 out_unlock:
3660 if (check->server)
3661 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3662 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003663}
3664
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003665
Christopher Faulet61cc8522020-04-20 14:54:42 +02003666/**************************************************************************/
3667/******************* Internals to parse tcp-check rules *******************/
3668/**************************************************************************/
3669struct action_kw_list tcp_check_keywords = {
3670 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3671};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003672
Christopher Faulet61cc8522020-04-20 14:54:42 +02003673/* Return the struct action_kw associated to a keyword */
3674static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003675{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003676 return action_lookup(&tcp_check_keywords.list, kw);
3677}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003678
Christopher Faulet61cc8522020-04-20 14:54:42 +02003679static void action_kw_tcp_check_build_list(struct buffer *chk)
3680{
3681 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003682}
3683
Christopher Faulet61cc8522020-04-20 14:54:42 +02003684/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3685 * returned on error.
3686 */
3687static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3688 struct list *rules, struct action_kw *kw,
3689 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003690{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003691 struct tcpcheck_rule *chk = NULL;
3692 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003693
Christopher Faulet61cc8522020-04-20 14:54:42 +02003694 actrule = calloc(1, sizeof(*actrule));
3695 if (!actrule) {
3696 memprintf(errmsg, "out of memory");
3697 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003698 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003699 actrule->kw = kw;
3700 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003701
Christopher Faulet61cc8522020-04-20 14:54:42 +02003702 cur_arg++;
3703 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3704 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3705 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003706 }
3707
Christopher Faulet61cc8522020-04-20 14:54:42 +02003708 chk = calloc(1, sizeof(*chk));
3709 if (!chk) {
3710 memprintf(errmsg, "out of memory");
3711 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003712 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003713 chk->action = TCPCHK_ACT_ACTION_KW;
3714 chk->action_kw.rule = actrule;
3715 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003716
3717 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003718 free(actrule);
3719 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003720}
3721
Christopher Faulet61cc8522020-04-20 14:54:42 +02003722/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3723 * returned on error.
3724 */
3725static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3726 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003727{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003728 struct tcpcheck_rule *chk = NULL;
3729 struct sockaddr_storage *sk = NULL;
3730 char *comment = NULL, *sni = NULL, *alpn = NULL;
3731 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003732 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003733 unsigned short conn_opts = 0;
3734 long port = 0;
3735 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003736
Christopher Faulet61cc8522020-04-20 14:54:42 +02003737 list_for_each_entry(chk, rules, list) {
3738 if (chk->action == TCPCHK_ACT_CONNECT)
3739 break;
3740 if (chk->action == TCPCHK_ACT_COMMENT ||
3741 chk->action == TCPCHK_ACT_ACTION_KW ||
3742 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3743 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003744
Christopher Faulet61cc8522020-04-20 14:54:42 +02003745 memprintf(errmsg, "first step MUST also be a 'connect', "
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05003746 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
Christopher Faulet61cc8522020-04-20 14:54:42 +02003747 "when there is a 'connect' step in the tcp-check ruleset");
3748 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003749 }
3750
Christopher Faulet61cc8522020-04-20 14:54:42 +02003751 cur_arg++;
3752 while (*(args[cur_arg])) {
3753 if (strcmp(args[cur_arg], "default") == 0)
3754 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3755 else if (strcmp(args[cur_arg], "addr") == 0) {
3756 int port1, port2;
3757 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003758
Christopher Faulet61cc8522020-04-20 14:54:42 +02003759 if (!*(args[cur_arg+1])) {
3760 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3761 goto error;
3762 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003763
Christopher Faulet61cc8522020-04-20 14:54:42 +02003764 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3765 if (!sk) {
3766 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3767 goto error;
3768 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003769
Christopher Faulet61cc8522020-04-20 14:54:42 +02003770 proto = protocol_by_family(sk->ss_family);
3771 if (!proto || !proto->connect) {
3772 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3773 args[cur_arg]);
3774 goto error;
3775 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003776
Christopher Faulet61cc8522020-04-20 14:54:42 +02003777 if (port1 != port2) {
3778 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3779 args[cur_arg], args[cur_arg+1]);
3780 goto error;
3781 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003782
Christopher Faulet61cc8522020-04-20 14:54:42 +02003783 cur_arg++;
3784 }
3785 else if (strcmp(args[cur_arg], "port") == 0) {
3786 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003787
Christopher Faulet61cc8522020-04-20 14:54:42 +02003788 if (!*(args[cur_arg+1])) {
3789 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3790 goto error;
3791 }
3792 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003793
Christopher Faulet61cc8522020-04-20 14:54:42 +02003794 port = 0;
3795 release_sample_expr(port_expr);
3796 p = args[cur_arg]; end = p + strlen(p);
3797 port = read_uint(&p, end);
3798 if (p != end) {
3799 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003800
Christopher Faulet61cc8522020-04-20 14:54:42 +02003801 px->conf.args.ctx = ARGC_SRV;
3802 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3803 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003804
Christopher Faulet61cc8522020-04-20 14:54:42 +02003805 if (!port_expr) {
3806 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3807 goto error;
3808 }
3809 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3810 memprintf(errmsg, "error detected while parsing port expression : "
3811 " fetch method '%s' extracts information from '%s', "
3812 "none of which is available here.\n",
3813 args[cur_arg], sample_src_names(port_expr->fetch->use));
3814 goto error;
3815 }
3816 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3817 }
3818 else if (port > 65535 || port < 1) {
3819 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3820 args[cur_arg]);
3821 goto error;
3822 }
3823 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003824 else if (strcmp(args[cur_arg], "proto") == 0) {
3825 if (!*(args[cur_arg+1])) {
3826 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3827 goto error;
3828 }
3829 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3830 if (!mux_proto) {
3831 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3832 goto error;
3833 }
3834 cur_arg++;
3835 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003836 else if (strcmp(args[cur_arg], "comment") == 0) {
3837 if (!*(args[cur_arg+1])) {
3838 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3839 goto error;
3840 }
3841 cur_arg++;
3842 free(comment);
3843 comment = strdup(args[cur_arg]);
3844 if (!comment) {
3845 memprintf(errmsg, "out of memory");
3846 goto error;
3847 }
3848 }
3849 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3850 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3851 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3852 conn_opts |= TCPCHK_OPT_SOCKS4;
3853 else if (strcmp(args[cur_arg], "linger") == 0)
3854 conn_opts |= TCPCHK_OPT_LINGER;
3855#ifdef USE_OPENSSL
3856 else if (strcmp(args[cur_arg], "ssl") == 0) {
3857 px->options |= PR_O_TCPCHK_SSL;
3858 conn_opts |= TCPCHK_OPT_SSL;
3859 }
3860 else if (strcmp(args[cur_arg], "sni") == 0) {
3861 if (!*(args[cur_arg+1])) {
3862 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3863 goto error;
3864 }
3865 cur_arg++;
3866 free(sni);
3867 sni = strdup(args[cur_arg]);
3868 if (!sni) {
3869 memprintf(errmsg, "out of memory");
3870 goto error;
3871 }
3872 }
3873 else if (strcmp(args[cur_arg], "alpn") == 0) {
3874#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3875 free(alpn);
3876 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3877 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3878 goto error;
3879 }
3880 cur_arg++;
3881#else
3882 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003883 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003884#endif
3885 }
3886#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003887
Christopher Faulet61cc8522020-04-20 14:54:42 +02003888 else {
3889 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3890#ifdef USE_OPENSSL
3891 ", 'ssl', 'sni', 'alpn'"
3892#endif /* USE_OPENSSL */
3893 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3894 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003895 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003896 }
3897 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003898 }
3899
Christopher Faulet61cc8522020-04-20 14:54:42 +02003900 chk = calloc(1, sizeof(*chk));
3901 if (!chk) {
3902 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003903 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003904 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003905 chk->action = TCPCHK_ACT_CONNECT;
3906 chk->comment = comment;
3907 chk->connect.port = port;
3908 chk->connect.options = conn_opts;
3909 chk->connect.sni = sni;
3910 chk->connect.alpn = alpn;
3911 chk->connect.alpn_len= alpn_len;
3912 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003913 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914 if (sk)
3915 chk->connect.addr = *sk;
3916 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003917
Christopher Faulet61cc8522020-04-20 14:54:42 +02003918 error:
3919 free(alpn);
3920 free(sni);
3921 free(comment);
3922 release_sample_expr(port_expr);
3923 return NULL;
3924}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003925
Christopher Faulet61cc8522020-04-20 14:54:42 +02003926/* Parses and creates a tcp-check send rule. NULL is returned on error */
3927static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3928 const char *file, int line, char **errmsg)
3929{
3930 struct tcpcheck_rule *chk = NULL;
3931 char *comment = NULL, *data = NULL;
3932 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003933
Christopher Fauletb50b3e62020-05-05 18:43:43 +02003934 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
3935 type = TCPCHK_SEND_BINARY_LF;
3936 else if (strcmp(args[cur_arg], "send-binary") == 0)
3937 type = TCPCHK_SEND_BINARY;
3938 else if (strcmp(args[cur_arg], "send-lf") == 0)
3939 type = TCPCHK_SEND_STRING_LF;
3940 else if (strcmp(args[cur_arg], "send") == 0)
3941 type = TCPCHK_SEND_STRING;
3942
Christopher Faulet61cc8522020-04-20 14:54:42 +02003943 if (!*(args[cur_arg+1])) {
3944 memprintf(errmsg, "'%s' expects a %s as argument",
3945 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003946 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003947 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003948
Christopher Faulet61cc8522020-04-20 14:54:42 +02003949 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003950
Christopher Faulet61cc8522020-04-20 14:54:42 +02003951 cur_arg += 2;
3952 while (*(args[cur_arg])) {
3953 if (strcmp(args[cur_arg], "comment") == 0) {
3954 if (!*(args[cur_arg+1])) {
3955 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3956 goto error;
3957 }
3958 cur_arg++;
3959 free(comment);
3960 comment = strdup(args[cur_arg]);
3961 if (!comment) {
3962 memprintf(errmsg, "out of memory");
3963 goto error;
3964 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003965 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003966 else {
Christopher Fauletb50b3e62020-05-05 18:43:43 +02003967 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003968 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003969 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003970 }
3971 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003972 }
3973
Christopher Faulet61cc8522020-04-20 14:54:42 +02003974 chk = calloc(1, sizeof(*chk));
3975 if (!chk) {
3976 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003977 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003978 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003979 chk->action = TCPCHK_ACT_SEND;
3980 chk->comment = comment;
3981 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003982
Christopher Faulet61cc8522020-04-20 14:54:42 +02003983 switch (chk->send.type) {
3984 case TCPCHK_SEND_STRING:
3985 chk->send.data = ist2(strdup(data), strlen(data));
3986 if (!isttest(chk->send.data)) {
3987 memprintf(errmsg, "out of memory");
3988 goto error;
3989 }
3990 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003991 case TCPCHK_SEND_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02003992 int len = chk->send.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003993 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003994 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3995 goto error;
3996 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003997 chk->send.data.len = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003998 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003999 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004000 case TCPCHK_SEND_STRING_LF:
4001 case TCPCHK_SEND_BINARY_LF:
4002 LIST_INIT(&chk->send.fmt);
4003 px->conf.args.ctx = ARGC_SRV;
4004 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4005 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4006 goto error;
4007 }
4008 break;
4009 case TCPCHK_SEND_HTTP:
4010 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004011 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004012 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004013
Christopher Faulet61cc8522020-04-20 14:54:42 +02004014 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004015
Christopher Faulet61cc8522020-04-20 14:54:42 +02004016 error:
4017 free(chk);
4018 free(comment);
4019 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004020}
4021
Christopher Faulet61cc8522020-04-20 14:54:42 +02004022/* Parses and creates a http-check send rule. NULL is returned on error */
4023static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
4024 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004025{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004026 struct tcpcheck_rule *chk = NULL;
4027 struct tcpcheck_http_hdr *hdr = NULL;
4028 struct http_hdr hdrs[global.tune.max_http_hdr];
4029 char *meth = NULL, *uri = NULL, *vsn = NULL;
4030 char *body = NULL, *comment = NULL;
4031 unsigned int flags = 0;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02004032 int i = 0, host_hdr = -1;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004033
4034 cur_arg++;
4035 while (*(args[cur_arg])) {
4036 if (strcmp(args[cur_arg], "meth") == 0) {
4037 if (!*(args[cur_arg+1])) {
4038 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4039 goto error;
4040 }
4041 cur_arg++;
4042 meth = args[cur_arg];
4043 }
Christopher Faulet7c95f5f2020-05-06 15:06:34 +02004044 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004045 if (!*(args[cur_arg+1])) {
4046 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4047 goto error;
4048 }
Christopher Faulet7c95f5f2020-05-06 15:06:34 +02004049 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4050 if (strcmp(args[cur_arg], "uri-lf") == 0)
4051 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004052 cur_arg++;
4053 uri = args[cur_arg];
Christopher Faulet61cc8522020-04-20 14:54:42 +02004054 }
Christopher Faulet907701b2020-04-28 09:37:00 +02004055 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004056 if (!*(args[cur_arg+1])) {
4057 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4058 goto error;
4059 }
4060 cur_arg++;
4061 vsn = args[cur_arg];
4062 }
4063 else if (strcmp(args[cur_arg], "hdr") == 0) {
4064 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
4065 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
4066 goto error;
4067 }
Christopher Faulet13ec87b2020-04-29 11:45:44 +02004068
4069 if (strcasecmp(args[cur_arg+1], "host") == 0) {
4070 if (host_hdr >= 0) {
4071 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
4072 args[cur_arg+1], istptr(hdrs[host_hdr].v));
4073 goto error;
4074 }
4075 host_hdr = i;
4076 }
Christopher Faulet7d765db2020-04-29 11:50:01 +02004077 else if (strcasecmp(args[cur_arg+1], "connection") == 0 ||
4078 strcasecmp(args[cur_arg+1], "content-length") == 0 ||
4079 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
4080 goto skip_hdr;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02004081
Christopher Faulet61cc8522020-04-20 14:54:42 +02004082 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
4083 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
4084 i++;
Christopher Faulet7d765db2020-04-29 11:50:01 +02004085 skip_hdr:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004086 cur_arg += 2;
4087 }
Christopher Faulet574e7bd2020-05-06 15:38:58 +02004088 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004089 if (!*(args[cur_arg+1])) {
4090 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4091 goto error;
4092 }
Christopher Faulet574e7bd2020-05-06 15:38:58 +02004093 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4094 if (strcmp(args[cur_arg], "body-lf") == 0)
4095 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004096 cur_arg++;
4097 body = args[cur_arg];
Christopher Faulet61cc8522020-04-20 14:54:42 +02004098 }
4099 else if (strcmp(args[cur_arg], "comment") == 0) {
4100 if (!*(args[cur_arg+1])) {
4101 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4102 goto error;
4103 }
4104 cur_arg++;
4105 free(comment);
4106 comment = strdup(args[cur_arg]);
4107 if (!comment) {
4108 memprintf(errmsg, "out of memory");
4109 goto error;
4110 }
4111 }
4112 else {
Christopher Faulet574e7bd2020-05-06 15:38:58 +02004113 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
Christopher Faulet7c95f5f2020-05-06 15:06:34 +02004114 " but got '%s' as argument.", args[cur_arg]);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004115 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004116 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004117 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004118 }
4119
Christopher Faulet61cc8522020-04-20 14:54:42 +02004120 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004121
Christopher Faulet61cc8522020-04-20 14:54:42 +02004122 chk = calloc(1, sizeof(*chk));
4123 if (!chk) {
4124 memprintf(errmsg, "out of memory");
4125 goto error;
4126 }
4127 chk->action = TCPCHK_ACT_SEND;
4128 chk->comment = comment; comment = NULL;
4129 chk->send.type = TCPCHK_SEND_HTTP;
4130 chk->send.http.flags = flags;
4131 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004132
Christopher Faulet61cc8522020-04-20 14:54:42 +02004133 if (meth) {
4134 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4135 chk->send.http.meth.str.area = strdup(meth);
4136 chk->send.http.meth.str.data = strlen(meth);
4137 if (!chk->send.http.meth.str.area) {
4138 memprintf(errmsg, "out of memory");
4139 goto error;
4140 }
4141 }
4142 if (uri) {
Christopher Faulet7c95f5f2020-05-06 15:06:34 +02004143 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
4144 LIST_INIT(&chk->send.http.uri_fmt);
4145 px->conf.args.ctx = ARGC_SRV;
4146 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4147 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
4148 goto error;
4149 }
4150 }
4151 else {
4152 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
4153 if (!isttest(chk->send.http.uri)) {
4154 memprintf(errmsg, "out of memory");
4155 goto error;
4156 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004157 }
4158 }
4159 if (vsn) {
4160 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
4161 if (!isttest(chk->send.http.vsn)) {
4162 memprintf(errmsg, "out of memory");
4163 goto error;
4164 }
4165 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02004166 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004167 hdr = calloc(1, sizeof(*hdr));
4168 if (!hdr) {
4169 memprintf(errmsg, "out of memory");
4170 goto error;
4171 }
4172 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02004173 hdr->name = istdup(hdrs[i].n);
4174 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004175 memprintf(errmsg, "out of memory");
4176 goto error;
4177 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004178
Christopher Fauletb61caf42020-04-21 10:57:42 +02004179 ist0(hdrs[i].v);
4180 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 +02004181 goto error;
4182 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4183 hdr = NULL;
4184 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004185
Christopher Faulet61cc8522020-04-20 14:54:42 +02004186 if (body) {
Christopher Faulet574e7bd2020-05-06 15:38:58 +02004187 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
4188 LIST_INIT(&chk->send.http.body_fmt);
4189 px->conf.args.ctx = ARGC_SRV;
4190 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4191 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
4192 goto error;
4193 }
4194 }
4195 else {
4196 chk->send.http.body = ist2(strdup(body), strlen(body));
4197 if (!isttest(chk->send.http.body)) {
4198 memprintf(errmsg, "out of memory");
4199 goto error;
4200 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004201 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004202 }
4203
Christopher Faulet61cc8522020-04-20 14:54:42 +02004204 return chk;
4205
4206 error:
4207 free_tcpcheck_http_hdr(hdr);
4208 free_tcpcheck(chk, 0);
4209 free(comment);
4210 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004211}
4212
Christopher Faulet61cc8522020-04-20 14:54:42 +02004213/* Parses and creates a http-check comment rule. NULL is returned on error */
4214static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4215 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004216{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004217 struct tcpcheck_rule *chk = NULL;
4218 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004219
Christopher Faulet61cc8522020-04-20 14:54:42 +02004220 if (!*(args[cur_arg+1])) {
4221 memprintf(errmsg, "expects a string as argument");
4222 goto error;
4223 }
4224 cur_arg++;
4225 comment = strdup(args[cur_arg]);
4226 if (!comment) {
4227 memprintf(errmsg, "out of memory");
4228 goto error;
4229 }
Willy Tarreau04276f32017-01-06 17:41:29 +01004230
Christopher Faulet61cc8522020-04-20 14:54:42 +02004231 chk = calloc(1, sizeof(*chk));
4232 if (!chk) {
4233 memprintf(errmsg, "out of memory");
4234 goto error;
4235 }
4236 chk->action = TCPCHK_ACT_COMMENT;
4237 chk->comment = comment;
4238 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004239
Christopher Faulet61cc8522020-04-20 14:54:42 +02004240 error:
4241 free(comment);
4242 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004243}
4244
Christopher Faulet61cc8522020-04-20 14:54:42 +02004245/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
4246 * on error. <proto> is set to the right protocol flags (covered by the
4247 * TCPCHK_RULES_PROTO_CHK mask).
4248 */
4249static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
4250 struct list *rules, unsigned int proto,
4251 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004252{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004253 struct tcpcheck_rule *prev_check, *chk = NULL;
4254 struct sample_expr *status_expr = NULL;
Christopher Faulet39708192020-05-05 10:47:36 +02004255 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004256 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004257 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
4258 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
4259 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
Christopher Faulet39708192020-05-05 10:47:36 +02004260 unsigned int flags = 0;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004261 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02004262 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004263
Christopher Faulet39708192020-05-05 10:47:36 +02004264 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02004265 if (!*(args[cur_arg+1])) {
4266 memprintf(errmsg, "expects at least a matching pattern as arguments");
4267 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004268 }
4269
Christopher Faulet61cc8522020-04-20 14:54:42 +02004270 cur_arg++;
4271 while (*(args[cur_arg])) {
4272 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004273
Christopher Faulet61cc8522020-04-20 14:54:42 +02004274 rescan:
4275 if (strcmp(args[cur_arg], "min-recv") == 0) {
4276 if (in_pattern) {
4277 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4278 goto error;
4279 }
4280 if (!*(args[cur_arg+1])) {
4281 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4282 goto error;
4283 }
4284 /* Use an signed integer here because of chksize */
4285 cur_arg++;
4286 min_recv = atol(args[cur_arg]);
4287 if (min_recv < -1 || min_recv > INT_MAX) {
4288 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4289 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02004290 }
4291 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004292 else if (*(args[cur_arg]) == '!') {
4293 in_pattern = 1;
4294 while (*(args[cur_arg]) == '!') {
4295 inverse = !inverse;
4296 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004297 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004298 if (!*(args[cur_arg]))
4299 cur_arg++;
4300 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02004301 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004302 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
4303 if (type != TCPCHK_EXPECT_UNDEF) {
4304 memprintf(errmsg, "only on pattern expected");
4305 goto error;
4306 }
4307 if (proto != TCPCHK_RULES_HTTP_CHK)
Christopher Faulet67a23452020-05-05 18:10:01 +02004308 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004309 else
Christopher Faulet67a23452020-05-05 18:10:01 +02004310 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
Christopher Faulete5870d82020-04-15 11:32:03 +02004311
Christopher Faulet61cc8522020-04-20 14:54:42 +02004312 if (!*(args[cur_arg+1])) {
4313 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4314 goto error;
4315 }
4316 cur_arg++;
4317 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004318 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004319 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4320 if (proto == TCPCHK_RULES_HTTP_CHK)
4321 goto bad_http_kw;
4322 if (type != TCPCHK_EXPECT_UNDEF) {
4323 memprintf(errmsg, "only on pattern expected");
4324 goto error;
4325 }
Christopher Faulet67a23452020-05-05 18:10:01 +02004326 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004327
Christopher Faulet61cc8522020-04-20 14:54:42 +02004328 if (!*(args[cur_arg+1])) {
4329 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4330 goto error;
4331 }
4332 cur_arg++;
4333 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004334 }
Christopher Fauletaaab0832020-05-05 15:54:22 +02004335 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
4336 if (type != TCPCHK_EXPECT_UNDEF) {
4337 memprintf(errmsg, "only on pattern expected");
4338 goto error;
4339 }
4340 if (proto != TCPCHK_RULES_HTTP_CHK)
4341 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
4342 else {
4343 if (*(args[cur_arg]) != 's')
4344 goto bad_http_kw;
4345 type = TCPCHK_EXPECT_HTTP_BODY_LF;
4346 }
4347
4348 if (!*(args[cur_arg+1])) {
4349 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4350 goto error;
4351 }
4352 cur_arg++;
4353 pattern = args[cur_arg];
4354 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004355 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4356 if (proto != TCPCHK_RULES_HTTP_CHK)
4357 goto bad_tcp_kw;
4358 if (type != TCPCHK_EXPECT_UNDEF) {
4359 memprintf(errmsg, "only on pattern expected");
4360 goto error;
4361 }
Christopher Faulet67a23452020-05-05 18:10:01 +02004362 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004363
Christopher Faulet61cc8522020-04-20 14:54:42 +02004364 if (!*(args[cur_arg+1])) {
4365 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4366 goto error;
4367 }
4368 cur_arg++;
4369 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004370 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004371 else if (strcmp(args[cur_arg], "custom") == 0) {
4372 if (in_pattern) {
4373 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4374 goto error;
4375 }
4376 if (type != TCPCHK_EXPECT_UNDEF) {
4377 memprintf(errmsg, "only on pattern expected");
4378 goto error;
4379 }
4380 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004381 }
Christopher Fauletb5594262020-05-05 20:23:13 +02004382 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
Christopher Faulet39708192020-05-05 10:47:36 +02004383 int orig_arg = cur_arg;
4384
4385 if (proto != TCPCHK_RULES_HTTP_CHK)
4386 goto bad_tcp_kw;
4387 if (type != TCPCHK_EXPECT_UNDEF) {
4388 memprintf(errmsg, "only on pattern expected");
4389 goto error;
4390 }
4391 type = TCPCHK_EXPECT_HTTP_HEADER;
4392
Christopher Fauletb5594262020-05-05 20:23:13 +02004393 if (strcmp(args[cur_arg], "fhdr") == 0)
4394 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
4395
Christopher Faulet39708192020-05-05 10:47:36 +02004396 /* Parse the name pattern, mandatory */
Christopher Fauletb5594262020-05-05 20:23:13 +02004397 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
4398 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
4399 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
Christopher Faulet39708192020-05-05 10:47:36 +02004400 args[orig_arg]);
4401 goto error;
4402 }
Christopher Fauletb5594262020-05-05 20:23:13 +02004403
4404 if (strcmp(args[cur_arg+1], "name-lf") == 0)
4405 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
4406
Christopher Faulet39708192020-05-05 10:47:36 +02004407 cur_arg += 2;
4408 if (strcmp(args[cur_arg], "-m") == 0) {
4409 if (!*(args[cur_arg+1])) {
4410 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
4411 args[orig_arg], args[cur_arg]);
4412 goto error;
4413 }
4414 if (strcmp(args[cur_arg+1], "str") == 0)
4415 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
4416 else if (strcmp(args[cur_arg+1], "beg") == 0)
4417 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
4418 else if (strcmp(args[cur_arg+1], "end") == 0)
4419 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
4420 else if (strcmp(args[cur_arg+1], "sub") == 0)
4421 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
Christopher Fauletb5594262020-05-05 20:23:13 +02004422 else if (strcmp(args[cur_arg+1], "reg") == 0) {
4423 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
4424 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
4425 args[orig_arg]);
4426 goto error;
4427 }
Christopher Faulet39708192020-05-05 10:47:36 +02004428 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
Christopher Fauletb5594262020-05-05 20:23:13 +02004429 }
Christopher Faulet39708192020-05-05 10:47:36 +02004430 else {
4431 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
4432 args[orig_arg], args[cur_arg], args[cur_arg+1]);
4433 goto error;
4434 }
4435 cur_arg += 2;
4436 }
4437 else
4438 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
4439 npat = args[cur_arg];
4440
Christopher Fauletb5594262020-05-05 20:23:13 +02004441 if (!*(args[cur_arg+1]) ||
4442 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
Christopher Faulet39708192020-05-05 10:47:36 +02004443 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
4444 goto next;
4445 }
Christopher Fauletb5594262020-05-05 20:23:13 +02004446 if (strcmp(args[cur_arg+1], "value-lf") == 0)
4447 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
Christopher Faulet39708192020-05-05 10:47:36 +02004448
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05004449 /* Parse the value pattern, optional */
Christopher Fauletb5594262020-05-05 20:23:13 +02004450 if (strcmp(args[cur_arg+2], "-m") == 0) {
4451 cur_arg += 2;
Christopher Faulet39708192020-05-05 10:47:36 +02004452 if (!*(args[cur_arg+1])) {
4453 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
4454 args[orig_arg], args[cur_arg]);
4455 goto error;
4456 }
4457 if (strcmp(args[cur_arg+1], "str") == 0)
4458 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
4459 else if (strcmp(args[cur_arg+1], "beg") == 0)
4460 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
4461 else if (strcmp(args[cur_arg+1], "end") == 0)
4462 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
4463 else if (strcmp(args[cur_arg+1], "sub") == 0)
4464 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
Christopher Fauletb5594262020-05-05 20:23:13 +02004465 else if (strcmp(args[cur_arg+1], "reg") == 0) {
4466 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
4467 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
4468 args[orig_arg]);
4469 goto error;
4470 }
Christopher Faulet39708192020-05-05 10:47:36 +02004471 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
Christopher Fauletb5594262020-05-05 20:23:13 +02004472 }
Christopher Faulet39708192020-05-05 10:47:36 +02004473 else {
4474 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
4475 args[orig_arg], args[cur_arg], args[cur_arg+1]);
4476 goto error;
4477 }
Christopher Faulet39708192020-05-05 10:47:36 +02004478 }
4479 else
4480 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
Christopher Faulet39708192020-05-05 10:47:36 +02004481
Christopher Fauletb5594262020-05-05 20:23:13 +02004482 if (!*(args[cur_arg+2])) {
4483 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
4484 goto error;
Christopher Faulet39708192020-05-05 10:47:36 +02004485 }
Christopher Fauletb5594262020-05-05 20:23:13 +02004486 vpat = args[cur_arg+2];
4487 cur_arg += 2;
Christopher Faulet39708192020-05-05 10:47:36 +02004488 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004489 else if (strcmp(args[cur_arg], "comment") == 0) {
4490 if (in_pattern) {
4491 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4492 goto error;
4493 }
4494 if (!*(args[cur_arg+1])) {
4495 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4496 goto error;
4497 }
4498 cur_arg++;
4499 free(comment);
4500 comment = strdup(args[cur_arg]);
4501 if (!comment) {
4502 memprintf(errmsg, "out of memory");
4503 goto error;
4504 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004505 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004506 else if (strcmp(args[cur_arg], "on-success") == 0) {
4507 if (in_pattern) {
4508 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4509 goto error;
4510 }
4511 if (!*(args[cur_arg+1])) {
4512 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4513 goto error;
4514 }
4515 cur_arg++;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004516 on_success_msg = args[cur_arg];
Christopher Fauletd7e63962020-04-17 20:15:59 +02004517 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004518 else if (strcmp(args[cur_arg], "on-error") == 0) {
4519 if (in_pattern) {
4520 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4521 goto error;
4522 }
4523 if (!*(args[cur_arg+1])) {
4524 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4525 goto error;
4526 }
4527 cur_arg++;
Christopher Faulet1941bab2020-05-05 07:55:50 +02004528 on_error_msg = args[cur_arg];
Christopher Faulet61cc8522020-04-20 14:54:42 +02004529 }
4530 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4531 if (in_pattern) {
4532 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4533 goto error;
4534 }
4535 if (!*(args[cur_arg+1])) {
4536 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4537 goto error;
4538 }
4539 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4540 ok_st = HCHK_STATUS_L7OKD;
4541 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4542 ok_st = HCHK_STATUS_L7OKCD;
4543 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4544 ok_st = HCHK_STATUS_L6OK;
4545 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4546 ok_st = HCHK_STATUS_L4OK;
4547 else {
4548 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4549 args[cur_arg], args[cur_arg+1]);
4550 goto error;
4551 }
4552 cur_arg++;
4553 }
4554 else if (strcmp(args[cur_arg], "error-status") == 0) {
4555 if (in_pattern) {
4556 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4557 goto error;
4558 }
4559 if (!*(args[cur_arg+1])) {
4560 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4561 goto error;
4562 }
4563 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4564 err_st = HCHK_STATUS_L7RSP;
4565 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4566 err_st = HCHK_STATUS_L7STS;
4567 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4568 err_st = HCHK_STATUS_L6RSP;
4569 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4570 err_st = HCHK_STATUS_L4CON;
4571 else {
4572 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4573 args[cur_arg], args[cur_arg+1]);
4574 goto error;
4575 }
4576 cur_arg++;
4577 }
4578 else if (strcmp(args[cur_arg], "status-code") == 0) {
4579 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004580
Christopher Faulet61cc8522020-04-20 14:54:42 +02004581 if (in_pattern) {
4582 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4583 goto error;
4584 }
4585 if (!*(args[cur_arg+1])) {
4586 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4587 goto error;
4588 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004589
Christopher Faulet61cc8522020-04-20 14:54:42 +02004590 cur_arg++;
4591 release_sample_expr(status_expr);
4592 px->conf.args.ctx = ARGC_SRV;
4593 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4594 file, line, errmsg, &px->conf.args, NULL);
4595 if (!status_expr) {
4596 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4597 goto error;
4598 }
4599 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4600 memprintf(errmsg, "error detected while parsing status-code expression : "
4601 " fetch method '%s' extracts information from '%s', "
4602 "none of which is available here.\n",
4603 args[cur_arg], sample_src_names(status_expr->fetch->use));
4604 goto error;
4605 }
4606 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4607 }
4608 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4609 if (in_pattern) {
4610 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4611 goto error;
4612 }
4613 if (!*(args[cur_arg+1])) {
4614 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4615 goto error;
4616 }
4617 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4618 tout_st = HCHK_STATUS_L7TOUT;
4619 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4620 tout_st = HCHK_STATUS_L6TOUT;
4621 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4622 tout_st = HCHK_STATUS_L4TOUT;
4623 else {
4624 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4625 args[cur_arg], args[cur_arg+1]);
4626 goto error;
4627 }
4628 cur_arg++;
4629 }
4630 else {
4631 if (proto == TCPCHK_RULES_HTTP_CHK) {
4632 bad_http_kw:
Christopher Fauletaaab0832020-05-05 15:54:22 +02004633 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
Christopher Fauletb5594262020-05-05 20:23:13 +02004634 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004635 }
4636 else {
4637 bad_tcp_kw:
Christopher Fauletaaab0832020-05-05 15:54:22 +02004638 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
4639 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004640 }
4641 goto error;
4642 }
Christopher Faulet39708192020-05-05 10:47:36 +02004643 next:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004644 cur_arg++;
4645 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004646
Christopher Faulet61cc8522020-04-20 14:54:42 +02004647 chk = calloc(1, sizeof(*chk));
4648 if (!chk) {
4649 memprintf(errmsg, "out of memory");
4650 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004651 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004652 chk->action = TCPCHK_ACT_EXPECT;
4653 LIST_INIT(&chk->expect.onerror_fmt);
4654 LIST_INIT(&chk->expect.onsuccess_fmt);
4655 chk->comment = comment; comment = NULL;
4656 chk->expect.type = type;
4657 chk->expect.min_recv = min_recv;
Christopher Faulet39708192020-05-05 10:47:36 +02004658 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004659 chk->expect.ok_status = ok_st;
4660 chk->expect.err_status = err_st;
4661 chk->expect.tout_status = tout_st;
4662 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004663
Christopher Faulet61cc8522020-04-20 14:54:42 +02004664 if (on_success_msg) {
4665 px->conf.args.ctx = ARGC_SRV;
4666 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4667 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4668 goto error;
4669 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004670 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004671 if (on_error_msg) {
4672 px->conf.args.ctx = ARGC_SRV;
4673 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4674 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4675 goto error;
4676 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004677 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004678
Christopher Faulet61cc8522020-04-20 14:54:42 +02004679 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004680 case TCPCHK_EXPECT_HTTP_STATUS: {
4681 const char *p = pattern;
4682 unsigned int c1,c2;
4683
4684 chk->expect.codes.codes = NULL;
4685 chk->expect.codes.num = 0;
4686 while (1) {
4687 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4688 if (*p == '-') {
4689 p++;
4690 c2 = read_uint(&p, pattern + strlen(pattern));
4691 }
4692 if (c1 > c2) {
4693 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4694 goto error;
4695 }
4696
4697 chk->expect.codes.num++;
4698 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4699 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4700 if (!chk->expect.codes.codes) {
4701 memprintf(errmsg, "out of memory");
4702 goto error;
4703 }
4704 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4705 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4706
4707 if (*p == '\0')
4708 break;
4709 if (*p != ',') {
4710 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4711 goto error;
4712 }
4713 p++;
4714 }
4715 break;
4716 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004717 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004718 case TCPCHK_EXPECT_HTTP_BODY:
4719 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004720 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004721 memprintf(errmsg, "out of memory");
4722 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004723 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004724 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004725 case TCPCHK_EXPECT_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02004726 int len = chk->expect.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004727
4728 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004729 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4730 goto error;
4731 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004732 chk->expect.data.len = len;
4733 break;
4734 }
Christopher Faulet67a23452020-05-05 18:10:01 +02004735 case TCPCHK_EXPECT_STRING_REGEX:
4736 case TCPCHK_EXPECT_BINARY_REGEX:
4737 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
4738 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004739 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004740 if (!chk->expect.regex)
4741 goto error;
Christopher Faulet39708192020-05-05 10:47:36 +02004742 break;
Christopher Fauletaaab0832020-05-05 15:54:22 +02004743
4744 case TCPCHK_EXPECT_STRING_LF:
4745 case TCPCHK_EXPECT_BINARY_LF:
4746 case TCPCHK_EXPECT_HTTP_BODY_LF:
4747 LIST_INIT(&chk->expect.fmt);
4748 px->conf.args.ctx = ARGC_SRV;
4749 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4750 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
4751 goto error;
4752 }
4753 break;
4754
Christopher Faulet39708192020-05-05 10:47:36 +02004755 case TCPCHK_EXPECT_HTTP_HEADER:
4756 if (!npat) {
4757 memprintf(errmsg, "unexpected error, undefined header name pattern");
4758 goto error;
4759 }
4760 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
4761 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
4762 if (!chk->expect.hdr.name_re)
4763 goto error;
4764 }
4765 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
4766 px->conf.args.ctx = ARGC_SRV;
4767 LIST_INIT(&chk->expect.hdr.name_fmt);
4768 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4769 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
4770 goto error;
4771 }
4772 }
4773 else {
4774 chk->expect.hdr.name = ist2(strdup(npat), strlen(npat));
4775 if (!isttest(chk->expect.hdr.name)) {
4776 memprintf(errmsg, "out of memory");
4777 goto error;
4778 }
4779 }
4780
4781 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
4782 chk->expect.hdr.value = IST_NULL;
4783 break;
4784 }
4785
4786 if (!vpat) {
4787 memprintf(errmsg, "unexpected error, undefined header value pattern");
4788 goto error;
4789 }
4790 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
4791 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
4792 if (!chk->expect.hdr.value_re)
4793 goto error;
4794 }
4795 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
4796 px->conf.args.ctx = ARGC_SRV;
4797 LIST_INIT(&chk->expect.hdr.value_fmt);
4798 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4799 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
4800 goto error;
4801 }
4802 }
4803 else {
4804 chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat));
4805 if (!isttest(chk->expect.hdr.value)) {
4806 memprintf(errmsg, "out of memory");
4807 goto error;
4808 }
4809 }
4810
Christopher Faulet61cc8522020-04-20 14:54:42 +02004811 break;
4812 case TCPCHK_EXPECT_CUSTOM:
4813 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4814 break;
4815 case TCPCHK_EXPECT_UNDEF:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004816 memprintf(errmsg, "pattern not found");
4817 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004818 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004819
Christopher Faulet61cc8522020-04-20 14:54:42 +02004820 /* All tcp-check expect points back to the first inverse expect rule in
4821 * a chain of one or more expect rule, potentially itself.
4822 */
4823 chk->expect.head = chk;
4824 list_for_each_entry_rev(prev_check, rules, list) {
4825 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4826 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4827 chk->expect.head = prev_check;
4828 continue;
4829 }
4830 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4831 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004832 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004833 return chk;
4834
4835 error:
4836 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004837 free(comment);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004838 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004839 return NULL;
4840}
4841
Christopher Faulet61cc8522020-04-20 14:54:42 +02004842/* Overwrites fields of the old http send rule with those of the new one. When
4843 * replaced, old values are freed and replaced by the new ones. New values are
4844 * not copied but transferred. At the end <new> should be empty and can be
4845 * safely released. This function never fails.
4846 */
4847static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004848{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004849 struct logformat_node *lf, *lfb;
4850 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004851
Christopher Faulet404f9192020-04-09 23:13:54 +02004852
Christopher Faulet61cc8522020-04-20 14:54:42 +02004853 if (new->send.http.meth.str.area) {
4854 free(old->send.http.meth.str.area);
4855 old->send.http.meth.meth = new->send.http.meth.meth;
4856 old->send.http.meth.str.area = new->send.http.meth.str.area;
4857 old->send.http.meth.str.data = new->send.http.meth.str.data;
4858 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004859 }
4860
Christopher Faulet61cc8522020-04-20 14:54:42 +02004861 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4862 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004863 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004864 else
4865 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4866 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4867 old->send.http.uri = new->send.http.uri;
4868 new->send.http.uri = IST_NULL;
4869 }
4870 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4871 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004872 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004873 else
4874 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4875 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4876 LIST_INIT(&old->send.http.uri_fmt);
4877 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4878 LIST_DEL(&lf->list);
4879 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4880 }
4881 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004882
Christopher Faulet61cc8522020-04-20 14:54:42 +02004883 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004884 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004885 old->send.http.vsn = new->send.http.vsn;
4886 new->send.http.vsn = IST_NULL;
4887 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004888
Christopher Faulet61cc8522020-04-20 14:54:42 +02004889 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4890 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4891 LIST_DEL(&hdr->list);
4892 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004893 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004894
4895 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4896 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004897 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004898 else
4899 free_tcpcheck_fmt(&old->send.http.body_fmt);
4900 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4901 old->send.http.body = new->send.http.body;
4902 new->send.http.body = IST_NULL;
4903 }
4904 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4905 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004906 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004907 else
4908 free_tcpcheck_fmt(&old->send.http.body_fmt);
4909 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4910 LIST_INIT(&old->send.http.body_fmt);
4911 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4912 LIST_DEL(&lf->list);
4913 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4914 }
4915 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004916}
4917
Christopher Faulet61cc8522020-04-20 14:54:42 +02004918/* Internal function used to add an http-check rule in a list during the config
4919 * parsing step. Depending on its type, and the previously inserted rules, a
4920 * specific action may be performed or an error may be reported. This functions
4921 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4922 * message.
4923 */
4924static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004925{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004926 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004927
Christopher Faulet61cc8522020-04-20 14:54:42 +02004928 /* the implicit send rule coming from an "option httpchk" line must be
4929 * merged with the first explici http-check send rule, if
4930 * any. Depdending the declaration order some tests are required.
4931 *
4932 * Some tests is also required for other kinds of http-check rules to be
4933 * sure the ruleset remains valid.
4934 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004935
Christopher Faulet61cc8522020-04-20 14:54:42 +02004936 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05004937 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
Christopher Faulet61cc8522020-04-20 14:54:42 +02004938 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4939 * following tests are performed :
4940 *
4941 * 1- If there is no such rule or if it is not a send rule, the implicit send
4942 * rule is pushed in front of the ruleset
4943 *
4944 * 2- If it is another implicit send rule, it is replaced with the new one.
4945 *
4946 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05004947 * both, overwriting the old send rule (the explicit one) with info of the
Christopher Faulet61cc8522020-04-20 14:54:42 +02004948 * new send rule (the implicit one).
4949 */
4950 r = get_first_tcpcheck_rule(rules);
4951 if (r && r->action == TCPCHK_ACT_CONNECT)
4952 r = get_next_tcpcheck_rule(rules, r);
4953 if (!r || r->action != TCPCHK_ACT_SEND)
4954 LIST_ADD(rules->list, &chk->list);
4955 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4956 LIST_DEL(&r->list);
4957 free_tcpcheck(r, 0);
4958 LIST_ADD(rules->list, &chk->list);
4959 }
4960 else {
4961 tcpcheck_overwrite_send_http_rule(r, chk);
4962 free_tcpcheck(chk, 0);
4963 }
4964 }
4965 else {
4966 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4967 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4968 * with an existing implicit send rule, if any. At the end, if there is no error,
4969 * the rule is appended to the list.
4970 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004971
Christopher Faulet61cc8522020-04-20 14:54:42 +02004972 r = get_last_tcpcheck_rule(rules);
4973 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4974 /* no error */;
4975 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4976 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4977 chk->index+1);
4978 return 0;
4979 }
Christopher Faulet1a200d62020-05-05 07:59:23 +02004980 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004981 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4982 chk->index+1);
4983 return 0;
4984 }
4985 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4986 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4987 chk->index+1);
4988 return 0;
4989 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004990
Christopher Faulet61cc8522020-04-20 14:54:42 +02004991 if (chk->action == TCPCHK_ACT_SEND) {
4992 r = get_first_tcpcheck_rule(rules);
4993 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4994 tcpcheck_overwrite_send_http_rule(r, chk);
4995 free_tcpcheck(chk, 0);
4996 LIST_DEL(&r->list);
4997 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4998 chk = r;
4999 }
5000 }
5001 LIST_ADDQ(rules->list, &chk->list);
5002 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02005003 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02005004}
5005
Christopher Faulet61cc8522020-04-20 14:54:42 +02005006/**************************************************************************/
5007/************************** Init/deinit checks ****************************/
5008/**************************************************************************/
5009static const char *init_check(struct check *check, int type)
5010{
5011 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02005012
Christopher Faulet61cc8522020-04-20 14:54:42 +02005013 b_reset(&check->bi); check->bi.size = global.tune.chksize;
5014 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02005015
Christopher Faulet61cc8522020-04-20 14:54:42 +02005016 check->bi.area = calloc(check->bi.size, sizeof(char));
5017 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02005018
Christopher Faulet61cc8522020-04-20 14:54:42 +02005019 if (!check->bi.area || !check->bo.area)
5020 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005021
Christopher Faulet61cc8522020-04-20 14:54:42 +02005022 check->wait_list.tasklet = tasklet_new();
5023 if (!check->wait_list.tasklet)
5024 return "out of memory while allocating check tasklet";
5025 check->wait_list.events = 0;
5026 check->wait_list.tasklet->process = event_srv_chk_io;
5027 check->wait_list.tasklet->context = check;
5028 return NULL;
5029}
5030
5031void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005032{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033 task_destroy(check->task);
5034 if (check->wait_list.tasklet)
5035 tasklet_free(check->wait_list.tasklet);
5036
5037 free(check->bi.area);
5038 free(check->bo.area);
5039 if (check->cs) {
5040 free(check->cs->conn);
5041 check->cs->conn = NULL;
5042 cs_free(check->cs);
5043 check->cs = NULL;
5044 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005045}
5046
Christopher Faulet61cc8522020-04-20 14:54:42 +02005047/* manages a server health-check. Returns the time the task accepts to wait, or
5048 * TIME_ETERNITY for infinity.
5049 */
5050static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005051{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005052 struct check *check = context;
5053
5054 if (check->type == PR_O2_EXT_CHK)
5055 return process_chk_proc(t, context, state);
5056 return process_chk_conn(t, context, state);
5057
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005058}
5059
Christopher Faulet61cc8522020-04-20 14:54:42 +02005060
5061static int start_check_task(struct check *check, int mininter,
5062 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005063{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005064 struct task *t;
5065 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005066
Christopher Faulet61cc8522020-04-20 14:54:42 +02005067 if (check->type == PR_O2_EXT_CHK)
5068 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005069
Christopher Faulet61cc8522020-04-20 14:54:42 +02005070 /* task for the check */
5071 if ((t = task_new(thread_mask)) == NULL) {
5072 ha_alert("Starting [%s:%s] check: out of memory.\n",
5073 check->server->proxy->id, check->server->id);
5074 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005075 }
5076
Christopher Faulet61cc8522020-04-20 14:54:42 +02005077 check->task = t;
5078 t->process = process_chk;
5079 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005080
Christopher Faulet61cc8522020-04-20 14:54:42 +02005081 if (mininter < srv_getinter(check))
5082 mininter = srv_getinter(check);
5083
5084 if (global.max_spread_checks && mininter > global.max_spread_checks)
5085 mininter = global.max_spread_checks;
5086
5087 /* check this every ms */
5088 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
5089 check->start = now;
5090 task_queue(t);
5091
5092 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005093}
5094
Christopher Faulet61cc8522020-04-20 14:54:42 +02005095/* updates the server's weight during a warmup stage. Once the final weight is
5096 * reached, the task automatically stops. Note that any server status change
5097 * must have updated s->last_change accordingly.
5098 */
5099static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005100{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005101 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005102
Christopher Faulet61cc8522020-04-20 14:54:42 +02005103 /* by default, plan on stopping the task */
5104 t->expire = TICK_ETERNITY;
5105 if ((s->next_admin & SRV_ADMF_MAINT) ||
5106 (s->next_state != SRV_ST_STARTING))
5107 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02005108
Christopher Faulet61cc8522020-04-20 14:54:42 +02005109 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005110
Christopher Faulet61cc8522020-04-20 14:54:42 +02005111 /* recalculate the weights and update the state */
5112 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02005113
Christopher Faulet61cc8522020-04-20 14:54:42 +02005114 /* probably that we can refill this server with a bit more connections */
5115 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02005116
Christopher Faulet61cc8522020-04-20 14:54:42 +02005117 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02005118
Christopher Faulet61cc8522020-04-20 14:54:42 +02005119 /* get back there in 1 second or 1/20th of the slowstart interval,
5120 * whichever is greater, resulting in small 5% steps.
5121 */
5122 if (s->next_state == SRV_ST_STARTING)
5123 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
5124 return t;
5125}
5126
5127/*
5128 * Start health-check.
5129 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
5130 */
5131static int start_checks()
5132{
5133
5134 struct proxy *px;
5135 struct server *s;
5136 struct task *t;
5137 int nbcheck=0, mininter=0, srvpos=0;
5138
5139 /* 0- init the dummy frontend used to create all checks sessions */
5140 init_new_proxy(&checks_fe);
5141 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
5142 checks_fe.mode = PR_MODE_TCP;
5143 checks_fe.maxconn = 0;
5144 checks_fe.conn_retries = CONN_RETRIES;
5145 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
5146 checks_fe.timeout.client = TICK_ETERNITY;
5147
5148 /* 1- count the checkers to run simultaneously.
5149 * We also determine the minimum interval among all of those which
5150 * have an interval larger than SRV_CHK_INTER_THRES. This interval
5151 * will be used to spread their start-up date. Those which have
5152 * a shorter interval will start independently and will not dictate
5153 * too short an interval for all others.
5154 */
5155 for (px = proxies_list; px; px = px->next) {
5156 for (s = px->srv; s; s = s->next) {
5157 if (s->slowstart) {
5158 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5159 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
5160 return ERR_ALERT | ERR_FATAL;
5161 }
5162 /* We need a warmup task that will be called when the server
5163 * state switches from down to up.
5164 */
5165 s->warmup = t;
5166 t->process = server_warmup;
5167 t->context = s;
5168 /* server can be in this state only because of */
5169 if (s->next_state == SRV_ST_STARTING)
5170 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 +02005171 }
5172
Christopher Faulet61cc8522020-04-20 14:54:42 +02005173 if (s->check.state & CHK_ST_CONFIGURED) {
5174 nbcheck++;
5175 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
5176 (!mininter || mininter > srv_getinter(&s->check)))
5177 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02005178 }
5179
Christopher Faulet61cc8522020-04-20 14:54:42 +02005180 if (s->agent.state & CHK_ST_CONFIGURED) {
5181 nbcheck++;
5182 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
5183 (!mininter || mininter > srv_getinter(&s->agent)))
5184 mininter = srv_getinter(&s->agent);
5185 }
Christopher Faulet5c288742020-03-31 08:15:58 +02005186 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005187 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02005188
Christopher Faulet61cc8522020-04-20 14:54:42 +02005189 if (!nbcheck)
5190 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02005191
Christopher Faulet61cc8522020-04-20 14:54:42 +02005192 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02005193
Christopher Faulet61cc8522020-04-20 14:54:42 +02005194 /*
5195 * 2- start them as far as possible from each others. For this, we will
5196 * start them after their interval set to the min interval divided by
5197 * the number of servers, weighted by the server's position in the list.
5198 */
5199 for (px = proxies_list; px; px = px->next) {
5200 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
5201 if (init_pid_list()) {
5202 ha_alert("Starting [%s] check: out of memory.\n", px->id);
5203 return ERR_ALERT | ERR_FATAL;
5204 }
5205 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02005206
Christopher Faulet61cc8522020-04-20 14:54:42 +02005207 for (s = px->srv; s; s = s->next) {
5208 /* A task for the main check */
5209 if (s->check.state & CHK_ST_CONFIGURED) {
5210 if (s->check.type == PR_O2_EXT_CHK) {
5211 if (!prepare_external_check(&s->check))
5212 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02005213 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005214 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
5215 return ERR_ALERT | ERR_FATAL;
5216 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02005217 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005218
Christopher Faulet61cc8522020-04-20 14:54:42 +02005219 /* A task for a auxiliary agent check */
5220 if (s->agent.state & CHK_ST_CONFIGURED) {
5221 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
5222 return ERR_ALERT | ERR_FATAL;
5223 }
5224 srvpos++;
5225 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005226 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005227 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005228 return 0;
5229}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005230
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005231
Christopher Faulet61cc8522020-04-20 14:54:42 +02005232/*
5233 * Return value:
5234 * the port to be used for the health check
5235 * 0 in case no port could be found for the check
5236 */
5237static int srv_check_healthcheck_port(struct check *chk)
5238{
5239 int i = 0;
5240 struct server *srv = NULL;
5241
5242 srv = chk->server;
5243
5244 /* by default, we use the health check port ocnfigured */
5245 if (chk->port > 0)
5246 return chk->port;
5247
5248 /* try to get the port from check_core.addr if check.port not set */
5249 i = get_host_port(&chk->addr);
5250 if (i > 0)
5251 return i;
5252
5253 /* try to get the port from server address */
5254 /* prevent MAPPORTS from working at this point, since checks could
5255 * not be performed in such case (MAPPORTS impose a relative ports
5256 * based on live traffic)
5257 */
5258 if (srv->flags & SRV_F_MAPPORTS)
5259 return 0;
5260
5261 i = srv->svc_port; /* by default */
5262 if (i > 0)
5263 return i;
5264
5265 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005266}
5267
Christopher Faulet61cc8522020-04-20 14:54:42 +02005268/* Initializes an health-check attached to the server <srv>. Non-zero is returned
5269 * if an error occurred.
5270 */
5271static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005272{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005273 const char *err;
5274 struct tcpcheck_rule *r;
5275 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005276
Christopher Faulet61cc8522020-04-20 14:54:42 +02005277 if (!srv->do_check)
5278 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005279
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005280
Christopher Faulet61cc8522020-04-20 14:54:42 +02005281 /* If neither a port nor an addr was specified and no check transport
5282 * layer is forced, then the transport layer used by the checks is the
5283 * same as for the production traffic. Otherwise we use raw_sock by
5284 * default, unless one is specified.
5285 */
5286 if (!srv->check.port && !is_addr(&srv->check.addr)) {
5287 if (!srv->check.use_ssl && srv->use_ssl != -1) {
5288 srv->check.use_ssl = srv->use_ssl;
5289 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005290 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005291 else if (srv->check.use_ssl == 1)
5292 srv->check.xprt = xprt_get(XPRT_SSL);
5293 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005294 }
5295
Christopher Faulet12882cf2020-04-23 15:50:18 +02005296 /* Inherit the mux protocol from the server if not already defined for
5297 * the check
5298 */
5299 if (srv->mux_proto && !srv->check.mux_proto)
5300 srv->check.mux_proto = srv->mux_proto;
5301
Christopher Faulet61cc8522020-04-20 14:54:42 +02005302 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005303
Christopher Faulet61cc8522020-04-20 14:54:42 +02005304 /* We need at least a service port, a check port or the first tcp-check
5305 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
5306 */
5307 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
5308 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
5309 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02005310
Christopher Faulet61cc8522020-04-20 14:54:42 +02005311 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
5312 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
5313 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5314 ret |= ERR_ALERT | ERR_ABORT;
5315 goto out;
5316 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005317
Christopher Faulet61cc8522020-04-20 14:54:42 +02005318 /* search the first action (connect / send / expect) in the list */
5319 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
5320 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
5321 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
5322 "nor tcp_check rule 'connect' with port information.\n",
5323 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5324 ret |= ERR_ALERT | ERR_ABORT;
5325 goto out;
5326 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005327
Christopher Faulet61cc8522020-04-20 14:54:42 +02005328 /* scan the tcp-check ruleset to ensure a port has been configured */
5329 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
5330 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
5331 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
5332 "and a tcp_check rule 'connect' with no port information.\n",
5333 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5334 ret |= ERR_ALERT | ERR_ABORT;
5335 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005336 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005337 }
5338
Christopher Faulet61cc8522020-04-20 14:54:42 +02005339 init:
5340 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
5341 struct tcpcheck_ruleset *rs = NULL;
5342 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
5343 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005344
Christopher Faulet61cc8522020-04-20 14:54:42 +02005345 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
5346 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02005347
Christopher Faulet61cc8522020-04-20 14:54:42 +02005348 rs = find_tcpcheck_ruleset("*tcp-check");
5349 if (!rs) {
5350 rs = create_tcpcheck_ruleset("*tcp-check");
5351 if (rs == NULL) {
5352 ha_alert("config: %s '%s': out of memory.\n",
5353 proxy_type_str(srv->proxy), srv->proxy->id);
5354 ret |= ERR_ALERT | ERR_FATAL;
5355 goto out;
5356 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005357 }
5358
Christopher Faulet61cc8522020-04-20 14:54:42 +02005359 free_tcpcheck_vars(&rules->preset_vars);
5360 rules->list = &rs->rules;
5361 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005362 }
5363
Christopher Faulet61cc8522020-04-20 14:54:42 +02005364 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
5365 if (err) {
5366 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
5367 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
5368 ret |= ERR_ALERT | ERR_ABORT;
5369 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005370 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005371 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
5372 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02005373
Christopher Faulet61cc8522020-04-20 14:54:42 +02005374 out:
5375 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005376}
5377
Christopher Faulet61cc8522020-04-20 14:54:42 +02005378/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
5379 * if an error occurred.
5380 */
5381static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02005382{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005383 struct tcpcheck_rule *chk;
5384 const char *err;
5385 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005386
Christopher Faulet61cc8522020-04-20 14:54:42 +02005387 if (!srv->do_agent)
5388 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005389
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05005390 /* If there is no connect rule preceding all send / expect rules, an
Christopher Faulet61cc8522020-04-20 14:54:42 +02005391 * implicit one is inserted before all others.
5392 */
5393 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
5394 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
5395 chk = calloc(1, sizeof(*chk));
5396 if (!chk) {
5397 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
5398 " to agent-check for server '%s' (out of memory).\n",
5399 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
5400 ret |= ERR_ALERT | ERR_FATAL;
5401 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005402 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005403 chk->action = TCPCHK_ACT_CONNECT;
5404 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
5405 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02005406 }
5407
Christopher Faulete5870d82020-04-15 11:32:03 +02005408
Christopher Faulet61cc8522020-04-20 14:54:42 +02005409 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
5410 if (err) {
5411 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
5412 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
5413 ret |= ERR_ALERT | ERR_ABORT;
5414 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005415 }
5416
Christopher Faulet61cc8522020-04-20 14:54:42 +02005417 if (!srv->agent.inter)
5418 srv->agent.inter = srv->check.inter;
5419
5420 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
5421 global.maxsock++;
5422
5423 out:
5424 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005425}
5426
Christopher Faulet61cc8522020-04-20 14:54:42 +02005427/* Check tcp-check health-check configuration for the proxy <px>. */
5428static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005429{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005430 struct tcpcheck_rule *chk, *back;
5431 char *comment = NULL, *errmsg = NULL;
5432 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
5433 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005434
Christopher Faulet61cc8522020-04-20 14:54:42 +02005435 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
5436 deinit_proxy_tcpcheck(px);
5437 goto out;
5438 }
5439
5440 free(px->check_command);
5441 free(px->check_path);
5442 px->check_command = px->check_path = NULL;
5443
5444 if (!px->tcpcheck_rules.list) {
5445 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
5446 ret |= ERR_ALERT | ERR_FATAL;
5447 goto out;
5448 }
5449
5450 /* HTTP ruleset only : */
5451 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5452 struct tcpcheck_rule *next;
5453
5454 /* move remaining implicit send rule from "option httpchk" line to the right place.
5455 * If such rule exists, it must be the first one. In this case, the rule is moved
5456 * after the first connect rule, if any. Otherwise, nothing is done.
5457 */
5458 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
5459 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5460 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
5461 if (next && next->action == TCPCHK_ACT_CONNECT) {
5462 LIST_DEL(&chk->list);
5463 LIST_ADD(&next->list, &chk->list);
5464 chk->index = next->index;
5465 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005466 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005467
5468 /* add implicit expect rule if the last one is a send. It is inherited from previous
5469 * versions where the http expect rule was optional. Now it is possible to chained
5470 * send/expect rules but the last expect may still be implicit.
5471 */
5472 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
5473 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02005474 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02005475 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
5476 px->conf.file, px->conf.line, &errmsg);
5477 if (!next) {
5478 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
5479 "(%s).\n", px->id, errmsg);
5480 free(errmsg);
5481 ret |= ERR_ALERT | ERR_FATAL;
5482 goto out;
5483 }
5484 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
5485 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02005486 }
5487 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005488
5489 /* For all ruleset: */
5490
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05005491 /* If there is no connect rule preceding all send / expect rules, an
Christopher Faulet61cc8522020-04-20 14:54:42 +02005492 * implicit one is inserted before all others.
5493 */
5494 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
5495 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
5496 chk = calloc(1, sizeof(*chk));
5497 if (!chk) {
5498 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
5499 "(out of memory).\n", px->id);
5500 ret |= ERR_ALERT | ERR_FATAL;
5501 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005502 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005503 chk->action = TCPCHK_ACT_CONNECT;
5504 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
5505 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
5506 }
5507
5508 /* Remove all comment rules. To do so, when a such rule is found, the
5509 * comment is assigned to the following rule(s).
5510 */
5511 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5512 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5513 free(comment);
5514 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005515 }
5516
Christopher Faulet61cc8522020-04-20 14:54:42 +02005517 prev_action = chk->action;
5518 switch (chk->action) {
5519 case TCPCHK_ACT_COMMENT:
5520 free(comment);
5521 comment = chk->comment;
5522 LIST_DEL(&chk->list);
5523 free(chk);
5524 break;
5525 case TCPCHK_ACT_CONNECT:
5526 if (!chk->comment && comment)
5527 chk->comment = strdup(comment);
5528 /* fall though */
5529 case TCPCHK_ACT_ACTION_KW:
5530 free(comment);
5531 comment = NULL;
5532 break;
5533 case TCPCHK_ACT_SEND:
5534 case TCPCHK_ACT_EXPECT:
5535 if (!chk->comment && comment)
5536 chk->comment = strdup(comment);
5537 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005538 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005539 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005540 free(comment);
5541 comment = NULL;
5542
5543 out:
5544 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005545}
5546
Christopher Faulet61cc8522020-04-20 14:54:42 +02005547void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005548{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005549 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5550 px->tcpcheck_rules.flags = 0;
5551 px->tcpcheck_rules.list = NULL;
5552}
Christopher Faulete5870d82020-04-15 11:32:03 +02005553
Christopher Faulet61cc8522020-04-20 14:54:42 +02005554static void deinit_srv_check(struct server *srv)
5555{
5556 if (srv->check.state & CHK_ST_CONFIGURED)
5557 free_check(&srv->check);
5558 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5559 srv->do_check = 0;
5560}
Christopher Faulete5870d82020-04-15 11:32:03 +02005561
Christopher Faulet61cc8522020-04-20 14:54:42 +02005562
5563static void deinit_srv_agent_check(struct server *srv)
5564{
5565 if (srv->agent.tcpcheck_rules) {
5566 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5567 free(srv->agent.tcpcheck_rules);
5568 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005569 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005570
Christopher Faulet61cc8522020-04-20 14:54:42 +02005571 if (srv->agent.state & CHK_ST_CONFIGURED)
5572 free_check(&srv->agent);
5573
5574 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5575 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005576}
5577
Christopher Faulet61cc8522020-04-20 14:54:42 +02005578static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005579{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005580 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005581 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005582 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005583
Christopher Fauletd7cee712020-04-21 13:45:00 +02005584 node = ebpt_first(&shared_tcpchecks);
5585 while (node) {
5586 next = ebpt_next(node);
5587 ebpt_delete(node);
5588 free(node->key);
5589 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005590 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5591 LIST_DEL(&r->list);
5592 free_tcpcheck(r, 0);
5593 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005594 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005595 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005596 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005597}
Christopher Faulete5870d82020-04-15 11:32:03 +02005598
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005599
Christopher Faulet61cc8522020-04-20 14:54:42 +02005600REGISTER_POST_SERVER_CHECK(init_srv_check);
5601REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5602REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5603REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005604
Christopher Faulet61cc8522020-04-20 14:54:42 +02005605REGISTER_SERVER_DEINIT(deinit_srv_check);
5606REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5607REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5608REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005609
Christopher Faulet61cc8522020-04-20 14:54:42 +02005610/**************************************************************************/
5611/****************************** Email alerts ******************************/
5612/* NOTE: It may be pertinent to use an applet to handle email alerts */
5613/* instead of a tcp-check ruleset */
5614/**************************************************************************/
5615void email_alert_free(struct email_alert *alert)
5616{
5617 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005618
Christopher Faulet61cc8522020-04-20 14:54:42 +02005619 if (!alert)
5620 return;
5621
5622 if (alert->rules.list) {
5623 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5624 LIST_DEL(&rule->list);
5625 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005626 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005627 free_tcpcheck_vars(&alert->rules.preset_vars);
5628 free(alert->rules.list);
5629 alert->rules.list = NULL;
5630 }
5631 pool_free(pool_head_email_alert, alert);
5632}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005633
Christopher Faulet61cc8522020-04-20 14:54:42 +02005634static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5635{
5636 struct check *check = context;
5637 struct email_alertq *q;
5638 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005639
Christopher Faulet61cc8522020-04-20 14:54:42 +02005640 q = container_of(check, typeof(*q), check);
5641
5642 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5643 while (1) {
5644 if (!(check->state & CHK_ST_ENABLED)) {
5645 if (LIST_ISEMPTY(&q->email_alerts)) {
5646 /* All alerts processed, queue the task */
5647 t->expire = TICK_ETERNITY;
5648 task_queue(t);
5649 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005650 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005651
5652 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5653 LIST_DEL(&alert->list);
5654 t->expire = now_ms;
5655 check->tcpcheck_rules = &alert->rules;
5656 check->status = HCHK_STATUS_INI;
5657 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005658 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005659
5660 process_chk(t, context, state);
5661 if (check->state & CHK_ST_INPROGRESS)
5662 break;
5663
5664 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5665 email_alert_free(alert);
5666 check->tcpcheck_rules = NULL;
5667 check->server = NULL;
5668 check->state &= ~CHK_ST_ENABLED;
5669 }
5670 end:
5671 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5672 return t;
5673}
5674
5675/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5676 *
5677 * The function returns 1 in success case, otherwise, it returns 0 and err is
5678 * filled.
5679 */
5680int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5681{
5682 struct mailer *mailer;
5683 struct email_alertq *queues;
5684 const char *err_str;
5685 int i = 0;
5686
5687 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5688 memprintf(err, "out of memory while allocating mailer alerts queues");
5689 goto fail_no_queue;
5690 }
5691
5692 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5693 struct email_alertq *q = &queues[i];
5694 struct check *check = &q->check;
5695 struct task *t;
5696
5697 LIST_INIT(&q->email_alerts);
5698 HA_SPIN_INIT(&q->lock);
5699 check->inter = mls->timeout.mail;
5700 check->rise = DEF_AGENT_RISETIME;
5701 check->proxy = p;
5702 check->fall = DEF_AGENT_FALLTIME;
5703 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5704 memprintf(err, "%s", err_str);
5705 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005706 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005707
5708 check->xprt = mailer->xprt;
5709 check->addr = mailer->addr;
5710 check->port = get_host_port(&mailer->addr);
5711
5712 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5713 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005714 goto error;
5715 }
5716
Christopher Faulet61cc8522020-04-20 14:54:42 +02005717 check->task = t;
5718 t->process = process_email_alert;
5719 t->context = check;
5720
5721 /* check this in one ms */
5722 t->expire = TICK_ETERNITY;
5723 check->start = now;
5724 task_queue(t);
5725 }
5726
5727 mls->users++;
5728 free(p->email_alert.mailers.name);
5729 p->email_alert.mailers.m = mls;
5730 p->email_alert.queues = queues;
5731 return 0;
5732
5733 error:
5734 for (i = 0; i < mls->count; i++) {
5735 struct email_alertq *q = &queues[i];
5736 struct check *check = &q->check;
5737
5738 free_check(check);
5739 }
5740 free(queues);
5741 fail_no_queue:
5742 return 1;
5743}
5744
5745static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5746{
5747 struct tcpcheck_rule *tcpcheck, *prev_check;
5748 struct tcpcheck_expect *expect;
5749
5750 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5751 return 0;
5752 memset(tcpcheck, 0, sizeof(*tcpcheck));
5753 tcpcheck->action = TCPCHK_ACT_EXPECT;
5754
5755 expect = &tcpcheck->expect;
5756 expect->type = TCPCHK_EXPECT_STRING;
5757 LIST_INIT(&expect->onerror_fmt);
5758 LIST_INIT(&expect->onsuccess_fmt);
5759 expect->ok_status = HCHK_STATUS_L7OKD;
5760 expect->err_status = HCHK_STATUS_L7RSP;
5761 expect->tout_status = HCHK_STATUS_L7TOUT;
5762 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005763 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005764 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5765 return 0;
5766 }
5767
5768 /* All tcp-check expect points back to the first inverse expect rule
5769 * in a chain of one or more expect rule, potentially itself.
5770 */
5771 tcpcheck->expect.head = tcpcheck;
5772 list_for_each_entry_rev(prev_check, rules->list, list) {
5773 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5774 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5775 tcpcheck->expect.head = prev_check;
5776 continue;
5777 }
5778 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5779 break;
5780 }
5781 LIST_ADDQ(rules->list, &tcpcheck->list);
5782 return 1;
5783}
5784
5785static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5786{
5787 struct tcpcheck_rule *tcpcheck;
5788 struct tcpcheck_send *send;
5789 const char *in;
5790 char *dst;
5791 int i;
5792
5793 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5794 return 0;
5795 memset(tcpcheck, 0, sizeof(*tcpcheck));
5796 tcpcheck->action = TCPCHK_ACT_SEND;
5797
5798 send = &tcpcheck->send;
5799 send->type = TCPCHK_SEND_STRING;
5800
5801 for (i = 0; strs[i]; i++)
5802 send->data.len += strlen(strs[i]);
5803
Christopher Fauletb61caf42020-04-21 10:57:42 +02005804 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005805 if (!isttest(send->data)) {
5806 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5807 return 0;
5808 }
5809
Christopher Fauletb61caf42020-04-21 10:57:42 +02005810 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005811 for (i = 0; strs[i]; i++)
5812 for (in = strs[i]; (*dst = *in++); dst++);
5813 *dst = 0;
5814
5815 LIST_ADDQ(rules->list, &tcpcheck->list);
5816 return 1;
5817}
5818
5819static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5820 struct email_alertq *q, const char *msg)
5821{
5822 struct email_alert *alert;
5823 struct tcpcheck_rule *tcpcheck;
5824 struct check *check = &q->check;
5825
5826 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5827 goto error;
5828 LIST_INIT(&alert->list);
5829 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5830 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5831 if (!alert->rules.list)
5832 goto error;
5833 LIST_INIT(alert->rules.list);
5834 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5835 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005836
Christopher Faulet61cc8522020-04-20 14:54:42 +02005837 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5838 goto error;
5839 memset(tcpcheck, 0, sizeof(*tcpcheck));
5840 tcpcheck->action = TCPCHK_ACT_CONNECT;
5841 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005842
Christopher Faulet61cc8522020-04-20 14:54:42 +02005843 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005844
Christopher Faulet61cc8522020-04-20 14:54:42 +02005845 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005846 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005847
5848 {
5849 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5850 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5851 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005852 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005853
Christopher Faulet61cc8522020-04-20 14:54:42 +02005854 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5855 goto error;
5856
5857 {
5858 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5859 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005860 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005861 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005862
5863 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5864 goto error;
5865
5866 {
5867 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5868 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005869 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005870 }
5871
Christopher Faulet61cc8522020-04-20 14:54:42 +02005872 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5873 goto error;
5874
5875 {
5876 const char * const strs[2] = { "DATA\r\n" };
5877 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005878 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005879 }
5880
5881 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5882 goto error;
5883
5884 {
5885 struct tm tm;
5886 char datestr[48];
5887 const char * const strs[18] = {
5888 "From: ", p->email_alert.from, "\r\n",
5889 "To: ", p->email_alert.to, "\r\n",
5890 "Date: ", datestr, "\r\n",
5891 "Subject: [HAproxy Alert] ", msg, "\r\n",
5892 "\r\n",
5893 msg, "\r\n",
5894 "\r\n",
5895 ".\r\n",
5896 NULL
5897 };
5898
5899 get_localtime(date.tv_sec, &tm);
5900
5901 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005902 goto error;
5903 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005904
5905 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005906 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005907 }
5908
5909 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005910 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005911
5912 {
5913 const char * const strs[2] = { "QUIT\r\n" };
5914 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5915 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005916 }
5917
Christopher Faulet61cc8522020-04-20 14:54:42 +02005918 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5919 goto error;
5920
5921 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5922 task_wakeup(check->task, TASK_WOKEN_MSG);
5923 LIST_ADDQ(&q->email_alerts, &alert->list);
5924 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5925 return 1;
5926
5927error:
5928 email_alert_free(alert);
5929 return 0;
5930}
5931
5932static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5933{
5934 int i;
5935 struct mailer *mailer;
5936
5937 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5938 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5939 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5940 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5941 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005942 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005943 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005944
Christopher Faulet61cc8522020-04-20 14:54:42 +02005945 return;
5946}
5947
5948/*
5949 * Send email alert if configured.
5950 */
5951void send_email_alert(struct server *s, int level, const char *format, ...)
5952{
5953 va_list argp;
5954 char buf[1024];
5955 int len;
5956 struct proxy *p = s->proxy;
5957
5958 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5959 return;
5960
5961 va_start(argp, format);
5962 len = vsnprintf(buf, sizeof(buf), format, argp);
5963 va_end(argp);
5964
5965 if (len < 0 || len >= sizeof(buf)) {
5966 ha_alert("Email alert [%s] could not format message\n", p->id);
5967 return;
5968 }
5969
5970 enqueue_email_alert(p, s, buf);
5971}
5972
5973/**************************************************************************/
5974/************************** Check sample fetches **************************/
5975/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005976
Christopher Faulet61cc8522020-04-20 14:54:42 +02005977static struct sample_fetch_kw_list smp_kws = {ILH, {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005978 { /* END */ },
5979}};
5980
5981INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5982
5983
5984/**************************************************************************/
5985/************************ Check's parsing functions ***********************/
5986/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005987/* Parses the "tcp-check" proxy keyword */
5988static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5989 struct proxy *defpx, const char *file, int line,
5990 char **errmsg)
5991{
Christopher Faulet404f9192020-04-09 23:13:54 +02005992 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005993 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005994 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005995
5996 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5997 ret = 1;
5998
Christopher Faulet404f9192020-04-09 23:13:54 +02005999 /* Deduce the ruleset name from the proxy info */
6000 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
6001 ((curpx == defpx) ? "defaults" : curpx->id),
6002 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02006003
Christopher Faulet61cc8522020-04-20 14:54:42 +02006004 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006005 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006006 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006007 if (rs == NULL) {
6008 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006009 goto error;
6010 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006011 }
6012
Gaetan Rivet5301b012020-02-25 17:19:17 +01006013 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02006014 if (!LIST_ISEMPTY(&rs->rules)) {
6015 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01006016 index = chk->index + 1;
6017 }
6018
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006019 cur_arg = 1;
6020 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02006021 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletb50b3e62020-05-05 18:43:43 +02006022 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
6023 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02006024 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006025 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02006026 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006027 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02006028 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006029 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01006030 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
6031
6032 if (!kw) {
6033 action_kw_tcp_check_build_list(&trash);
6034 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
6035 "%s%s. but got '%s'",
6036 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
6037 goto error;
6038 }
Christopher Faulet404f9192020-04-09 23:13:54 +02006039 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006040 }
6041
6042 if (!chk) {
6043 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
6044 goto error;
6045 }
Christopher Faulet528f4812020-04-28 10:47:28 +02006046 ret = (ret || (*errmsg != NULL)); /* Handle warning */
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006047
6048 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01006049 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02006050 LIST_ADDQ(&rs->rules, &chk->list);
6051
6052 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02006053 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02006054 /* Use this ruleset if the proxy already has tcp-check enabled */
6055 curpx->tcpcheck_rules.list = &rs->rules;
6056 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
6057 }
6058 else {
6059 /* mark this ruleset as unused for now */
6060 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
6061 }
6062
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006063 return ret;
6064
6065 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02006066 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006067 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006068 return -1;
6069}
6070
Christopher Faulet51b129f2020-04-09 15:54:18 +02006071/* Parses the "http-check" proxy keyword */
6072static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
6073 struct proxy *defpx, const char *file, int line,
6074 char **errmsg)
6075{
Christopher Faulete5870d82020-04-15 11:32:03 +02006076 struct tcpcheck_ruleset *rs = NULL;
6077 struct tcpcheck_rule *chk = NULL;
6078 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006079
6080 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
6081 ret = 1;
6082
6083 cur_arg = 1;
6084 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
6085 /* enable a graceful server shutdown on an HTTP 404 response */
6086 curpx->options |= PR_O_DISABLE404;
6087 if (too_many_args(1, args, errmsg, NULL))
6088 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02006089 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006090 }
6091 else if (strcmp(args[cur_arg], "send-state") == 0) {
6092 /* enable emission of the apparent state of a server in HTTP checks */
6093 curpx->options2 |= PR_O2_CHK_SNDST;
6094 if (too_many_args(1, args, errmsg, NULL))
6095 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02006096 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006097 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02006098
Christopher Faulete5870d82020-04-15 11:32:03 +02006099 /* Deduce the ruleset name from the proxy info */
6100 chunk_printf(&trash, "*http-check-%s_%s-%d",
6101 ((curpx == defpx) ? "defaults" : curpx->id),
6102 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02006103
Christopher Faulet61cc8522020-04-20 14:54:42 +02006104 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006105 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006106 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006107 if (rs == NULL) {
6108 memprintf(errmsg, "out of memory.\n");
6109 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006110 }
6111 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02006112
Christopher Faulete5870d82020-04-15 11:32:03 +02006113 index = 0;
6114 if (!LIST_ISEMPTY(&rs->rules)) {
6115 chk = LIST_PREV(&rs->rules, typeof(chk), list);
6116 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
6117 index = chk->index + 1;
6118 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02006119
Christopher Faulete5870d82020-04-15 11:32:03 +02006120 if (strcmp(args[cur_arg], "connect") == 0)
6121 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
6122 else if (strcmp(args[cur_arg], "send") == 0)
6123 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
6124 else if (strcmp(args[cur_arg], "expect") == 0)
6125 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
6126 file, line, errmsg);
6127 else if (strcmp(args[cur_arg], "comment") == 0)
6128 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
6129 else {
6130 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02006131
Christopher Faulete5870d82020-04-15 11:32:03 +02006132 if (!kw) {
6133 action_kw_tcp_check_build_list(&trash);
6134 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
6135 " 'send', 'expect'%s%s. but got '%s'",
6136 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
6137 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006138 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006139 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
6140 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02006141
Christopher Faulete5870d82020-04-15 11:32:03 +02006142 if (!chk) {
6143 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
6144 goto error;
6145 }
6146 ret = (*errmsg != NULL); /* Handle warning */
6147
6148 chk->index = index;
6149 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
6150 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
6151 /* Use this ruleset if the proxy already has http-check enabled */
6152 curpx->tcpcheck_rules.list = &rs->rules;
6153 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
6154 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
6155 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
6156 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02006157 goto error;
6158 }
6159 }
6160 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02006161 /* mark this ruleset as unused for now */
6162 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
6163 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02006164 }
6165
Christopher Faulete5870d82020-04-15 11:32:03 +02006166 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02006167 return ret;
6168
6169 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02006170 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006171 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02006172 return -1;
6173}
6174
Christopher Faulete9111b62020-04-09 18:12:08 +02006175/* Parses the "external-check" proxy keyword */
6176static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
6177 struct proxy *defpx, const char *file, int line,
6178 char **errmsg)
6179{
6180 int cur_arg, ret = 0;
6181
6182 cur_arg = 1;
6183 if (!*(args[cur_arg])) {
6184 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
6185 goto error;
6186 }
6187
6188 if (strcmp(args[cur_arg], "command") == 0) {
6189 if (too_many_args(2, args, errmsg, NULL))
6190 goto error;
6191 if (!*(args[cur_arg+1])) {
6192 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
6193 goto error;
6194 }
6195 free(curpx->check_command);
6196 curpx->check_command = strdup(args[cur_arg+1]);
6197 }
6198 else if (strcmp(args[cur_arg], "path") == 0) {
6199 if (too_many_args(2, args, errmsg, NULL))
6200 goto error;
6201 if (!*(args[cur_arg+1])) {
6202 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
6203 goto error;
6204 }
6205 free(curpx->check_path);
6206 curpx->check_path = strdup(args[cur_arg+1]);
6207 }
6208 else {
6209 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
6210 args[0], args[1]);
6211 goto error;
6212 }
6213
6214 ret = (*errmsg != NULL); /* Handle warning */
6215 return ret;
6216
6217error:
6218 return -1;
6219}
Christopher Faulet33f05df2020-04-01 11:08:50 +02006220
Christopher Faulet430e4802020-04-09 15:28:16 +02006221/* Parses the "option tcp-check" proxy keyword */
6222int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6223 const char *file, int line)
6224{
Christopher Faulet404f9192020-04-09 23:13:54 +02006225 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02006226 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6227 int err_code = 0;
6228
6229 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6230 err_code |= ERR_WARN;
6231
6232 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6233 goto out;
6234
Christopher Faulet404f9192020-04-09 23:13:54 +02006235 curpx->options2 &= ~PR_O2_CHK_ANY;
6236 curpx->options2 |= PR_O2_TCPCHK_CHK;
6237
Christopher Fauletd7e63962020-04-17 20:15:59 +02006238 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02006239 /* If a tcp-check rulesset is already set, do nothing */
6240 if (rules->list)
6241 goto out;
6242
6243 /* If a tcp-check ruleset is waiting to be used for the current proxy,
6244 * get it.
6245 */
6246 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
6247 goto curpx_ruleset;
6248
6249 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
6250 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006251 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006252 if (rs)
6253 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02006254 }
6255
Christopher Faulet404f9192020-04-09 23:13:54 +02006256 curpx_ruleset:
6257 /* Deduce the ruleset name from the proxy info */
6258 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
6259 ((curpx == defpx) ? "defaults" : curpx->id),
6260 curpx->conf.file, curpx->conf.line);
6261
Christopher Faulet61cc8522020-04-20 14:54:42 +02006262 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006263 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006264 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02006265 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02006266 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6267 goto error;
6268 }
Christopher Faulet430e4802020-04-09 15:28:16 +02006269 }
6270
Christopher Faulet404f9192020-04-09 23:13:54 +02006271 ruleset_found:
6272 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02006273 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02006274 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02006275
6276 out:
6277 return err_code;
6278
6279 error:
6280 err_code |= ERR_ALERT | ERR_FATAL;
6281 goto out;
6282}
Christopher Faulet33f05df2020-04-01 11:08:50 +02006283
6284/* Parses the "option redis-check" proxy keyword */
6285int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6286 const char *file, int line)
6287{
6288 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
6289 static char *redis_res = "+PONG\r\n";
6290
6291 struct tcpcheck_ruleset *rs = NULL;
6292 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6293 struct tcpcheck_rule *chk;
6294 char *errmsg = NULL;
6295 int err_code = 0;
6296
6297 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6298 err_code |= ERR_WARN;
6299
6300 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6301 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02006302
6303 curpx->options2 &= ~PR_O2_CHK_ANY;
6304 curpx->options2 |= PR_O2_TCPCHK_CHK;
6305
6306 free_tcpcheck_vars(&rules->preset_vars);
6307 rules->list = NULL;
6308 rules->flags = 0;
6309
Christopher Faulet61cc8522020-04-20 14:54:42 +02006310 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02006311 if (rs)
6312 goto ruleset_found;
6313
Christopher Faulet61cc8522020-04-20 14:54:42 +02006314 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02006315 if (rs == NULL) {
6316 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6317 goto error;
6318 }
6319
6320 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
6321 1, curpx, &rs->rules, file, line, &errmsg);
6322 if (!chk) {
6323 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6324 goto error;
6325 }
6326 chk->index = 0;
6327 LIST_ADDQ(&rs->rules, &chk->list);
6328
6329 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
6330 "error-status", "L7STS",
Christopher Faulete596d182020-05-05 17:46:34 +02006331 "on-error", "%[res.payload(0,0),cut_crlf]",
Christopher Faulet33f05df2020-04-01 11:08:50 +02006332 "on-success", "Redis server is ok",
6333 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006334 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006335 if (!chk) {
6336 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6337 goto error;
6338 }
6339 chk->index = 1;
6340 LIST_ADDQ(&rs->rules, &chk->list);
6341
Christopher Fauletd7cee712020-04-21 13:45:00 +02006342 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006343
6344 ruleset_found:
6345 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006346 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02006347
6348 out:
6349 free(errmsg);
6350 return err_code;
6351
6352 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006353 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02006354 err_code |= ERR_ALERT | ERR_FATAL;
6355 goto out;
6356}
6357
Christopher Faulet811f78c2020-04-01 11:10:27 +02006358
6359/* Parses the "option ssl-hello-chk" proxy keyword */
6360int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6361 const char *file, int line)
6362{
6363 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
6364 * ssl-hello-chk option to ensure that the remote server speaks SSL.
6365 *
6366 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
6367 */
6368 static char sslv3_client_hello[] = {
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05006369 "16" /* ContentType : 0x16 = Handshake */
Christopher Faulet811f78c2020-04-01 11:10:27 +02006370 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
6371 "0079" /* ContentLength : 0x79 bytes after this one */
6372 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
6373 "000075" /* HandshakeLength : 0x75 bytes after this one */
6374 "0300" /* Hello Version : 0x0300 = v3 */
6375 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
6376 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
6377 "00" /* Session ID length : empty (no session ID) */
6378 "004E" /* Cipher Suite Length : 78 bytes after this one */
6379 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
6380 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
6381 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
6382 "000D" "000E" "000F" "0010" /* various bit lengths, */
6383 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
6384 "0015" "0016" "0017" "0018"
6385 "0019" "001A" "001B" "002F"
6386 "0030" "0031" "0032" "0033"
6387 "0034" "0035" "0036" "0037"
6388 "0038" "0039" "003A"
6389 "01" /* Compression Length : 0x01 = 1 byte for types */
6390 "00" /* Compression Type : 0x00 = NULL compression */
6391 };
6392
6393 struct tcpcheck_ruleset *rs = NULL;
6394 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6395 struct tcpcheck_rule *chk;
6396 char *errmsg = NULL;
6397 int err_code = 0;
6398
6399 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6400 err_code |= ERR_WARN;
6401
6402 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6403 goto out;
6404
Christopher Faulet811f78c2020-04-01 11:10:27 +02006405 curpx->options2 &= ~PR_O2_CHK_ANY;
6406 curpx->options2 |= PR_O2_TCPCHK_CHK;
6407
6408 free_tcpcheck_vars(&rules->preset_vars);
6409 rules->list = NULL;
6410 rules->flags = 0;
6411
Christopher Faulet61cc8522020-04-20 14:54:42 +02006412 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02006413 if (rs)
6414 goto ruleset_found;
6415
Christopher Faulet61cc8522020-04-20 14:54:42 +02006416 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02006417 if (rs == NULL) {
6418 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6419 goto error;
6420 }
6421
Christopher Fauletb50b3e62020-05-05 18:43:43 +02006422 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
Christopher Faulet811f78c2020-04-01 11:10:27 +02006423 1, curpx, &rs->rules, file, line, &errmsg);
6424 if (!chk) {
6425 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6426 goto error;
6427 }
6428 chk->index = 0;
6429 LIST_ADDQ(&rs->rules, &chk->list);
6430
6431 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02006432 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02006433 "error-status", "L6RSP", "tout-status", "L6TOUT",
6434 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006435 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006436 if (!chk) {
6437 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6438 goto error;
6439 }
6440 chk->index = 1;
6441 LIST_ADDQ(&rs->rules, &chk->list);
6442
Christopher Fauletd7cee712020-04-21 13:45:00 +02006443 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006444
6445 ruleset_found:
6446 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006447 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02006448
6449 out:
6450 free(errmsg);
6451 return err_code;
6452
6453 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006454 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006455 err_code |= ERR_ALERT | ERR_FATAL;
6456 goto out;
6457}
6458
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006459/* Parses the "option smtpchk" proxy keyword */
6460int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6461 const char *file, int line)
6462{
6463 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
6464
6465 struct tcpcheck_ruleset *rs = NULL;
6466 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6467 struct tcpcheck_rule *chk;
6468 struct tcpcheck_var *var = NULL;
6469 char *cmd = NULL, *errmsg = NULL;
6470 int err_code = 0;
6471
6472 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6473 err_code |= ERR_WARN;
6474
6475 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6476 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006477
6478 curpx->options2 &= ~PR_O2_CHK_ANY;
6479 curpx->options2 |= PR_O2_TCPCHK_CHK;
6480
6481 free_tcpcheck_vars(&rules->preset_vars);
6482 rules->list = NULL;
6483 rules->flags = 0;
6484
6485 cur_arg += 2;
6486 if (*args[cur_arg] && *args[cur_arg+1] &&
6487 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6488 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6489 if (cmd)
6490 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6491 }
6492 else {
6493 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6494 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6495 cmd = strdup("HELO localhost");
6496 }
6497
Christopher Fauletb61caf42020-04-21 10:57:42 +02006498 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006499 if (cmd == NULL || var == NULL) {
6500 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6501 goto error;
6502 }
6503 var->data.type = SMP_T_STR;
6504 var->data.u.str.area = cmd;
6505 var->data.u.str.data = strlen(cmd);
6506 LIST_INIT(&var->list);
6507 LIST_ADDQ(&rules->preset_vars, &var->list);
6508 cmd = NULL;
6509 var = NULL;
6510
Christopher Faulet61cc8522020-04-20 14:54:42 +02006511 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006512 if (rs)
6513 goto ruleset_found;
6514
Christopher Faulet61cc8522020-04-20 14:54:42 +02006515 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006516 if (rs == NULL) {
6517 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6518 goto error;
6519 }
6520
6521 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6522 1, curpx, &rs->rules, file, line, &errmsg);
6523 if (!chk) {
6524 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6525 goto error;
6526 }
6527 chk->index = 0;
6528 LIST_ADDQ(&rs->rules, &chk->list);
6529
6530 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6531 "min-recv", "4",
6532 "error-status", "L7RSP",
Christopher Faulete596d182020-05-05 17:46:34 +02006533 "on-error", "%[res.payload(0,0),cut_crlf]",
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006534 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006535 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006536 if (!chk) {
6537 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6538 goto error;
6539 }
6540 chk->index = 1;
6541 LIST_ADDQ(&rs->rules, &chk->list);
6542
6543 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6544 "min-recv", "4",
6545 "error-status", "L7STS",
Christopher Faulete596d182020-05-05 17:46:34 +02006546 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
6547 "status-code", "res.payload(0,3)",
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006548 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006549 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006550 if (!chk) {
6551 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6552 goto error;
6553 }
6554 chk->index = 2;
6555 LIST_ADDQ(&rs->rules, &chk->list);
6556
Christopher Fauletb50b3e62020-05-05 18:43:43 +02006557 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006558 1, curpx, &rs->rules, file, line, &errmsg);
6559 if (!chk) {
6560 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6561 goto error;
6562 }
6563 chk->index = 3;
6564 LIST_ADDQ(&rs->rules, &chk->list);
6565
6566 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6567 "min-recv", "4",
6568 "error-status", "L7STS",
Christopher Faulete596d182020-05-05 17:46:34 +02006569 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
6570 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
6571 "status-code", "res.payload(0,3)",
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006572 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006573 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006574 if (!chk) {
6575 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6576 goto error;
6577 }
6578 chk->index = 4;
6579 LIST_ADDQ(&rs->rules, &chk->list);
6580
Christopher Fauletd7cee712020-04-21 13:45:00 +02006581 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006582
6583 ruleset_found:
6584 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006585 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006586
6587 out:
6588 free(errmsg);
6589 return err_code;
6590
6591 error:
6592 free(cmd);
6593 free(var);
6594 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006595 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006596 err_code |= ERR_ALERT | ERR_FATAL;
6597 goto out;
6598}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006599
Christopher Fauletce355072020-04-02 11:44:39 +02006600/* Parses the "option pgsql-check" proxy keyword */
6601int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6602 const char *file, int line)
6603{
6604 static char pgsql_req[] = {
6605 "%[var(check.plen),htonl,hex]" /* The packet length*/
6606 "00030000" /* the version 3.0 */
6607 "7573657200" /* "user" key */
6608 "%[var(check.username),hex]00" /* the username */
6609 "00"
6610 };
6611
6612 struct tcpcheck_ruleset *rs = NULL;
6613 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6614 struct tcpcheck_rule *chk;
6615 struct tcpcheck_var *var = NULL;
6616 char *user = NULL, *errmsg = NULL;
6617 size_t packetlen = 0;
6618 int err_code = 0;
6619
6620 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6621 err_code |= ERR_WARN;
6622
6623 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6624 goto out;
6625
Christopher Fauletce355072020-04-02 11:44:39 +02006626 curpx->options2 &= ~PR_O2_CHK_ANY;
6627 curpx->options2 |= PR_O2_TCPCHK_CHK;
6628
6629 free_tcpcheck_vars(&rules->preset_vars);
6630 rules->list = NULL;
6631 rules->flags = 0;
6632
6633 cur_arg += 2;
6634 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6635 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6636 file, line, args[0], args[1]);
6637 goto error;
6638 }
6639 if (strcmp(args[cur_arg], "user") == 0) {
6640 packetlen = 15 + strlen(args[cur_arg+1]);
6641 user = strdup(args[cur_arg+1]);
6642
Christopher Fauletb61caf42020-04-21 10:57:42 +02006643 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006644 if (user == NULL || var == NULL) {
6645 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6646 goto error;
6647 }
6648 var->data.type = SMP_T_STR;
6649 var->data.u.str.area = user;
6650 var->data.u.str.data = strlen(user);
6651 LIST_INIT(&var->list);
6652 LIST_ADDQ(&rules->preset_vars, &var->list);
6653 user = NULL;
6654 var = NULL;
6655
Christopher Fauletb61caf42020-04-21 10:57:42 +02006656 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006657 if (var == NULL) {
6658 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6659 goto error;
6660 }
6661 var->data.type = SMP_T_SINT;
6662 var->data.u.sint = packetlen;
6663 LIST_INIT(&var->list);
6664 LIST_ADDQ(&rules->preset_vars, &var->list);
6665 var = NULL;
6666 }
6667 else {
6668 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6669 file, line, args[0], args[1]);
6670 goto error;
6671 }
6672
Christopher Faulet61cc8522020-04-20 14:54:42 +02006673 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006674 if (rs)
6675 goto ruleset_found;
6676
Christopher Faulet61cc8522020-04-20 14:54:42 +02006677 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006678 if (rs == NULL) {
6679 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6680 goto error;
6681 }
6682
6683 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6684 1, curpx, &rs->rules, file, line, &errmsg);
6685 if (!chk) {
6686 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6687 goto error;
6688 }
6689 chk->index = 0;
6690 LIST_ADDQ(&rs->rules, &chk->list);
6691
Christopher Fauletb50b3e62020-05-05 18:43:43 +02006692 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
Christopher Fauletce355072020-04-02 11:44:39 +02006693 1, curpx, &rs->rules, file, line, &errmsg);
6694 if (!chk) {
6695 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6696 goto error;
6697 }
6698 chk->index = 1;
6699 LIST_ADDQ(&rs->rules, &chk->list);
6700
6701 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6702 "min-recv", "5",
6703 "error-status", "L7RSP",
Christopher Faulete596d182020-05-05 17:46:34 +02006704 "on-error", "%[res.payload(6,0)]",
Christopher Fauletce355072020-04-02 11:44:39 +02006705 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006706 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006707 if (!chk) {
6708 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6709 goto error;
6710 }
6711 chk->index = 2;
6712 LIST_ADDQ(&rs->rules, &chk->list);
6713
Christopher Fauletb841c742020-04-27 18:29:49 +02006714 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 +02006715 "min-recv", "9",
6716 "error-status", "L7STS",
6717 "on-success", "PostgreSQL server is ok",
6718 "on-error", "PostgreSQL unknown error",
6719 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006720 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006721 if (!chk) {
6722 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6723 goto error;
6724 }
6725 chk->index = 3;
6726 LIST_ADDQ(&rs->rules, &chk->list);
6727
Christopher Fauletd7cee712020-04-21 13:45:00 +02006728 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006729
6730 ruleset_found:
6731 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006732 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006733
6734 out:
6735 free(errmsg);
6736 return err_code;
6737
6738 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006739 free(user);
6740 free(var);
6741 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006742 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006743 err_code |= ERR_ALERT | ERR_FATAL;
6744 goto out;
6745}
6746
6747
6748/* Parses the "option mysql-check" proxy keyword */
6749int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6750 const char *file, int line)
6751{
6752 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6753 * const char mysql40_client_auth_pkt[] = {
6754 * "\x0e\x00\x00" // packet length
6755 * "\x01" // packet number
6756 * "\x00\x00" // client capabilities
6757 * "\x00\x00\x01" // max packet
6758 * "haproxy\x00" // username (null terminated string)
6759 * "\x00" // filler (always 0x00)
6760 * "\x01\x00\x00" // packet length
6761 * "\x00" // packet number
6762 * "\x01" // COM_QUIT command
6763 * };
6764 */
6765 static char mysql40_rsname[] = "*mysql40-check";
6766 static char mysql40_req[] = {
6767 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6768 "0080" /* client capabilities */
6769 "000001" /* max packet */
6770 "%[var(check.username),hex]00" /* the username */
6771 "00" /* filler (always 0x00) */
6772 "010000" /* packet length*/
6773 "00" /* sequence ID */
6774 "01" /* COM_QUIT command */
6775 };
6776
6777 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6778 * const char mysql41_client_auth_pkt[] = {
6779 * "\x0e\x00\x00\" // packet length
6780 * "\x01" // packet number
6781 * "\x00\x00\x00\x00" // client capabilities
6782 * "\x00\x00\x00\x01" // max packet
6783 * "\x21" // character set (UTF-8)
6784 * char[23] // All zeroes
6785 * "haproxy\x00" // username (null terminated string)
6786 * "\x00" // filler (always 0x00)
6787 * "\x01\x00\x00" // packet length
6788 * "\x00" // packet number
6789 * "\x01" // COM_QUIT command
6790 * };
6791 */
6792 static char mysql41_rsname[] = "*mysql41-check";
6793 static char mysql41_req[] = {
6794 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6795 "00820000" /* client capabilities */
6796 "00800001" /* max packet */
6797 "21" /* character set (UTF-8) */
6798 "000000000000000000000000" /* 23 bytes, al zeroes */
6799 "0000000000000000000000"
6800 "%[var(check.username),hex]00" /* the username */
6801 "00" /* filler (always 0x00) */
6802 "010000" /* packet length*/
6803 "00" /* sequence ID */
6804 "01" /* COM_QUIT command */
6805 };
6806
6807 struct tcpcheck_ruleset *rs = NULL;
6808 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6809 struct tcpcheck_rule *chk;
6810 struct tcpcheck_var *var = NULL;
6811 char *mysql_rsname = "*mysql-check";
6812 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6813 int index = 0, err_code = 0;
6814
6815 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6816 err_code |= ERR_WARN;
6817
6818 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6819 goto out;
6820
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006821 curpx->options2 &= ~PR_O2_CHK_ANY;
6822 curpx->options2 |= PR_O2_TCPCHK_CHK;
6823
6824 free_tcpcheck_vars(&rules->preset_vars);
6825 rules->list = NULL;
6826 rules->flags = 0;
6827
6828 cur_arg += 2;
6829 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006830 int packetlen, userlen;
6831
6832 if (strcmp(args[cur_arg], "user") != 0) {
6833 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6834 file, line, args[0], args[1], args[cur_arg]);
6835 goto error;
6836 }
6837
6838 if (*(args[cur_arg+1]) == 0) {
6839 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6840 file, line, args[0], args[1], args[cur_arg]);
6841 goto error;
6842 }
6843
6844 hdr = calloc(4, sizeof(*hdr));
6845 user = strdup(args[cur_arg+1]);
6846 userlen = strlen(args[cur_arg+1]);
6847
6848 if (hdr == NULL || user == NULL) {
6849 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6850 goto error;
6851 }
6852
6853 if (*args[cur_arg+2]) {
6854 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6855 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6856 file, line, args[cur_arg], args[cur_arg+2]);
6857 goto error;
6858 }
6859 packetlen = userlen + 7 + 27;
6860 mysql_req = mysql41_req;
6861 mysql_rsname = mysql41_rsname;
6862 }
6863 else {
6864 packetlen = userlen + 7;
6865 mysql_req = mysql40_req;
6866 mysql_rsname = mysql40_rsname;
6867 }
6868
6869 hdr[0] = (unsigned char)(packetlen & 0xff);
6870 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6871 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6872 hdr[3] = 1;
6873
Christopher Fauletb61caf42020-04-21 10:57:42 +02006874 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006875 if (var == NULL) {
6876 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6877 goto error;
6878 }
6879 var->data.type = SMP_T_STR;
6880 var->data.u.str.area = hdr;
6881 var->data.u.str.data = 4;
6882 LIST_INIT(&var->list);
6883 LIST_ADDQ(&rules->preset_vars, &var->list);
6884 hdr = NULL;
6885 var = NULL;
6886
Christopher Fauletb61caf42020-04-21 10:57:42 +02006887 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006888 if (var == NULL) {
6889 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6890 goto error;
6891 }
6892 var->data.type = SMP_T_STR;
6893 var->data.u.str.area = user;
6894 var->data.u.str.data = strlen(user);
6895 LIST_INIT(&var->list);
6896 LIST_ADDQ(&rules->preset_vars, &var->list);
6897 user = NULL;
6898 var = NULL;
6899 }
6900
Christopher Faulet61cc8522020-04-20 14:54:42 +02006901 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006902 if (rs)
6903 goto ruleset_found;
6904
Christopher Faulet61cc8522020-04-20 14:54:42 +02006905 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006906 if (rs == NULL) {
6907 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6908 goto error;
6909 }
6910
6911 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6912 1, curpx, &rs->rules, file, line, &errmsg);
6913 if (!chk) {
6914 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6915 goto error;
6916 }
6917 chk->index = index++;
6918 LIST_ADDQ(&rs->rules, &chk->list);
6919
6920 if (mysql_req) {
Christopher Fauletb50b3e62020-05-05 18:43:43 +02006921 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006922 1, curpx, &rs->rules, file, line, &errmsg);
6923 if (!chk) {
6924 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6925 goto error;
6926 }
6927 chk->index = index++;
6928 LIST_ADDQ(&rs->rules, &chk->list);
6929 }
6930
6931 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006932 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006933 if (!chk) {
6934 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6935 goto error;
6936 }
6937 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6938 chk->index = index++;
6939 LIST_ADDQ(&rs->rules, &chk->list);
6940
6941 if (mysql_req) {
6942 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006943 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006944 if (!chk) {
6945 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6946 goto error;
6947 }
6948 chk->expect.custom = tcpcheck_mysql_expect_ok;
6949 chk->index = index++;
6950 LIST_ADDQ(&rs->rules, &chk->list);
6951 }
6952
Christopher Fauletd7cee712020-04-21 13:45:00 +02006953 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006954
6955 ruleset_found:
6956 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006957 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006958
6959 out:
6960 free(errmsg);
6961 return err_code;
6962
6963 error:
6964 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006965 free(user);
6966 free(var);
6967 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006968 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006969 err_code |= ERR_ALERT | ERR_FATAL;
6970 goto out;
6971}
6972
Christopher Faulet1997eca2020-04-03 23:13:50 +02006973int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6974 const char *file, int line)
6975{
6976 static char *ldap_req = "300C020101600702010304008000";
6977
6978 struct tcpcheck_ruleset *rs = NULL;
6979 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6980 struct tcpcheck_rule *chk;
6981 char *errmsg = NULL;
6982 int err_code = 0;
6983
6984 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6985 err_code |= ERR_WARN;
6986
6987 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6988 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006989
6990 curpx->options2 &= ~PR_O2_CHK_ANY;
6991 curpx->options2 |= PR_O2_TCPCHK_CHK;
6992
6993 free_tcpcheck_vars(&rules->preset_vars);
6994 rules->list = NULL;
6995 rules->flags = 0;
6996
Christopher Faulet61cc8522020-04-20 14:54:42 +02006997 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006998 if (rs)
6999 goto ruleset_found;
7000
Christopher Faulet61cc8522020-04-20 14:54:42 +02007001 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02007002 if (rs == NULL) {
7003 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
7004 goto error;
7005 }
7006
7007 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
7008 1, curpx, &rs->rules, file, line, &errmsg);
7009 if (!chk) {
7010 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
7011 goto error;
7012 }
7013 chk->index = 0;
7014 LIST_ADDQ(&rs->rules, &chk->list);
7015
7016 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
7017 "min-recv", "14",
7018 "on-error", "Not LDAPv3 protocol",
7019 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02007020 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02007021 if (!chk) {
7022 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
7023 goto error;
7024 }
7025 chk->index = 1;
7026 LIST_ADDQ(&rs->rules, &chk->list);
7027
7028 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02007029 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02007030 if (!chk) {
7031 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
7032 goto error;
7033 }
7034 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
7035 chk->index = 2;
7036 LIST_ADDQ(&rs->rules, &chk->list);
7037
Christopher Fauletd7cee712020-04-21 13:45:00 +02007038 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02007039
7040 ruleset_found:
7041 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007042 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02007043
7044 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02007045 free(errmsg);
7046 return err_code;
7047
7048 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02007049 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02007050 err_code |= ERR_ALERT | ERR_FATAL;
7051 goto out;
7052}
7053
7054int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
7055 const char *file, int line)
7056{
7057 struct tcpcheck_ruleset *rs = NULL;
7058 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
7059 struct tcpcheck_rule *chk;
7060 char *spop_req = NULL;
7061 char *errmsg = NULL;
7062 int spop_len = 0, err_code = 0;
7063
7064 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
7065 err_code |= ERR_WARN;
7066
7067 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
7068 goto out;
7069
Christopher Faulet267b01b2020-04-04 10:27:09 +02007070 curpx->options2 &= ~PR_O2_CHK_ANY;
7071 curpx->options2 |= PR_O2_TCPCHK_CHK;
7072
7073 free_tcpcheck_vars(&rules->preset_vars);
7074 rules->list = NULL;
7075 rules->flags = 0;
7076
7077
Christopher Faulet61cc8522020-04-20 14:54:42 +02007078 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02007079 if (rs)
7080 goto ruleset_found;
7081
Christopher Faulet61cc8522020-04-20 14:54:42 +02007082 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02007083 if (rs == NULL) {
7084 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
7085 goto error;
7086 }
7087
7088 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
7089 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
7090 goto error;
7091 }
7092 chunk_reset(&trash);
7093 dump_binary(&trash, spop_req, spop_len);
7094 trash.area[trash.data] = '\0';
7095
7096 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
7097 1, curpx, &rs->rules, file, line, &errmsg);
7098 if (!chk) {
7099 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
7100 goto error;
7101 }
7102 chk->index = 0;
7103 LIST_ADDQ(&rs->rules, &chk->list);
7104
7105 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02007106 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02007107 if (!chk) {
7108 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
7109 goto error;
7110 }
7111 chk->expect.custom = tcpcheck_spop_expect_agenthello;
7112 chk->index = 1;
7113 LIST_ADDQ(&rs->rules, &chk->list);
7114
Christopher Fauletd7cee712020-04-21 13:45:00 +02007115 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02007116
7117 ruleset_found:
7118 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007119 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02007120
7121 out:
7122 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02007123 free(errmsg);
7124 return err_code;
7125
7126 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02007127 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02007128 err_code |= ERR_ALERT | ERR_FATAL;
7129 goto out;
7130}
Christopher Fauletce355072020-04-02 11:44:39 +02007131
Christopher Faulete5870d82020-04-15 11:32:03 +02007132
7133struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
7134{
7135 struct tcpcheck_rule *chk = NULL;
7136 struct tcpcheck_http_hdr *hdr = NULL;
7137 char *meth = NULL, *uri = NULL, *vsn = NULL;
7138 char *hdrs, *body;
7139
7140 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
7141 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
7142 if (hdrs == body)
7143 hdrs = NULL;
7144 if (hdrs) {
7145 *hdrs = '\0';
7146 hdrs +=2;
7147 }
7148 if (body) {
7149 *body = '\0';
7150 body += 4;
7151 }
7152 if (hdrs || body) {
7153 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
7154 " Please, consider to use 'http-check send' directive instead.");
7155 }
7156
7157 chk = calloc(1, sizeof(*chk));
7158 if (!chk) {
7159 memprintf(errmsg, "out of memory");
7160 goto error;
7161 }
7162 chk->action = TCPCHK_ACT_SEND;
7163 chk->send.type = TCPCHK_SEND_HTTP;
7164 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
7165 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
7166 LIST_INIT(&chk->send.http.hdrs);
7167
7168 /* Copy the method, uri and version */
7169 if (*args[cur_arg]) {
7170 if (!*args[cur_arg+1])
7171 uri = args[cur_arg];
7172 else
7173 meth = args[cur_arg];
7174 }
7175 if (*args[cur_arg+1])
7176 uri = args[cur_arg+1];
7177 if (*args[cur_arg+2])
7178 vsn = args[cur_arg+2];
7179
7180 if (meth) {
7181 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
7182 chk->send.http.meth.str.area = strdup(meth);
7183 chk->send.http.meth.str.data = strlen(meth);
7184 if (!chk->send.http.meth.str.area) {
7185 memprintf(errmsg, "out of memory");
7186 goto error;
7187 }
7188 }
7189 if (uri) {
7190 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007191 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007192 memprintf(errmsg, "out of memory");
7193 goto error;
7194 }
7195 }
7196 if (vsn) {
7197 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007198 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007199 memprintf(errmsg, "out of memory");
7200 goto error;
7201 }
7202 }
7203
7204 /* Copy the header */
7205 if (hdrs) {
7206 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
7207 struct h1m h1m;
7208 int i, ret;
7209
7210 /* Build and parse the request */
7211 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
7212
7213 h1m.flags = H1_MF_HDRS_ONLY;
7214 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
7215 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
7216 &h1m, NULL);
7217 if (ret <= 0) {
7218 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
7219 goto error;
7220 }
7221
Christopher Fauletb61caf42020-04-21 10:57:42 +02007222 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007223 hdr = calloc(1, sizeof(*hdr));
7224 if (!hdr) {
7225 memprintf(errmsg, "out of memory");
7226 goto error;
7227 }
7228 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007229 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02007230 if (!hdr->name.ptr) {
7231 memprintf(errmsg, "out of memory");
7232 goto error;
7233 }
7234
Christopher Fauletb61caf42020-04-21 10:57:42 +02007235 ist0(tmp_hdrs[i].v);
7236 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 +02007237 goto error;
7238 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
7239 }
7240 }
7241
7242 /* Copy the body */
7243 if (body) {
7244 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02007245 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02007246 memprintf(errmsg, "out of memory");
7247 goto error;
7248 }
7249 }
7250
7251 return chk;
7252
7253 error:
7254 free_tcpcheck_http_hdr(hdr);
7255 free_tcpcheck(chk, 0);
7256 return NULL;
7257}
7258
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007259int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
7260 const char *file, int line)
7261{
Christopher Faulete5870d82020-04-15 11:32:03 +02007262 struct tcpcheck_ruleset *rs = NULL;
7263 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
7264 struct tcpcheck_rule *chk;
7265 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007266 int err_code = 0;
7267
7268 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
7269 err_code |= ERR_WARN;
7270
7271 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
7272 goto out;
7273
Christopher Faulete5870d82020-04-15 11:32:03 +02007274 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
7275 if (!chk) {
7276 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
7277 goto error;
7278 }
7279 if (errmsg) {
7280 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
7281 err_code |= ERR_WARN;
7282 free(errmsg);
7283 errmsg = NULL;
7284 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007285
Christopher Faulete5870d82020-04-15 11:32:03 +02007286 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007287 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02007288 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007289
Christopher Faulete5870d82020-04-15 11:32:03 +02007290 free_tcpcheck_vars(&rules->preset_vars);
7291 rules->list = NULL;
7292 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007293
Christopher Faulete5870d82020-04-15 11:32:03 +02007294 /* Deduce the ruleset name from the proxy info */
7295 chunk_printf(&trash, "*http-check-%s_%s-%d",
7296 ((curpx == defpx) ? "defaults" : curpx->id),
7297 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007298
Christopher Faulet61cc8522020-04-20 14:54:42 +02007299 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02007300 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02007301 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02007302 if (rs == NULL) {
7303 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
7304 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007305 }
Christopher Faulete5870d82020-04-15 11:32:03 +02007306 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007307
Christopher Faulete5870d82020-04-15 11:32:03 +02007308 rules->list = &rs->rules;
7309 rules->flags |= TCPCHK_RULES_HTTP_CHK;
7310 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
7311 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
7312 rules->list = NULL;
7313 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007314 }
Christopher Faulete5870d82020-04-15 11:32:03 +02007315
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007316 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02007317 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007318 return err_code;
7319
7320 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02007321 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02007322 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02007323 err_code |= ERR_ALERT | ERR_FATAL;
7324 goto out;
7325}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007326
Christopher Faulet6f557912020-04-09 15:58:50 +02007327int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
7328 const char *file, int line)
7329{
7330 int err_code = 0;
7331
Christopher Faulet6f557912020-04-09 15:58:50 +02007332 curpx->options2 &= ~PR_O2_CHK_ANY;
7333 curpx->options2 |= PR_O2_EXT_CHK;
7334 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
7335 goto out;
7336
7337 out:
7338 return err_code;
7339}
7340
Christopher Fauletce8111e2020-04-06 15:04:11 +02007341/* Parse the "addr" server keyword */
7342static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7343 char **errmsg)
7344{
7345 struct sockaddr_storage *sk;
7346 struct protocol *proto;
7347 int port1, port2, err_code = 0;
7348
7349
7350 if (!*args[*cur_arg+1]) {
7351 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
7352 goto error;
7353 }
7354
7355 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
7356 if (!sk) {
7357 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
7358 goto error;
7359 }
7360
7361 proto = protocol_by_family(sk->ss_family);
7362 if (!proto || !proto->connect) {
7363 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
7364 args[*cur_arg], args[*cur_arg+1]);
7365 goto error;
7366 }
7367
7368 if (port1 != port2) {
7369 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
7370 args[*cur_arg], args[*cur_arg+1]);
7371 goto error;
7372 }
7373
7374 srv->check.addr = srv->agent.addr = *sk;
7375 srv->flags |= SRV_F_CHECKADDR;
7376 srv->flags |= SRV_F_AGENTADDR;
7377
7378 out:
7379 return err_code;
7380
7381 error:
7382 err_code |= ERR_ALERT | ERR_FATAL;
7383 goto out;
7384}
7385
7386
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007387/* Parse the "agent-addr" server keyword */
7388static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7389 char **errmsg)
7390{
7391 int err_code = 0;
7392
7393 if (!*(args[*cur_arg+1])) {
7394 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
7395 goto error;
7396 }
7397 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
7398 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
7399 goto error;
7400 }
7401
7402 out:
7403 return err_code;
7404
7405 error:
7406 err_code |= ERR_ALERT | ERR_FATAL;
7407 goto out;
7408}
7409
7410/* Parse the "agent-check" server keyword */
7411static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7412 char **errmsg)
7413{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007414 struct tcpcheck_ruleset *rs = NULL;
7415 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7416 struct tcpcheck_rule *chk;
7417 int err_code = 0;
7418
7419 if (srv->do_agent)
7420 goto out;
7421
7422 if (!rules) {
7423 rules = calloc(1, sizeof(*rules));
7424 if (!rules) {
7425 memprintf(errmsg, "out of memory.");
7426 goto error;
7427 }
7428 LIST_INIT(&rules->preset_vars);
7429 srv->agent.tcpcheck_rules = rules;
7430 }
7431 rules->list = NULL;
7432 rules->flags = 0;
7433
Christopher Faulet61cc8522020-04-20 14:54:42 +02007434 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007435 if (rs)
7436 goto ruleset_found;
7437
Christopher Faulet61cc8522020-04-20 14:54:42 +02007438 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007439 if (rs == NULL) {
7440 memprintf(errmsg, "out of memory.");
7441 goto error;
7442 }
7443
Christopher Fauletb50b3e62020-05-05 18:43:43 +02007444 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", "%[var(check.agent_string)]", ""},
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007445 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
7446 if (!chk) {
7447 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
7448 goto error;
7449 }
7450 chk->index = 0;
7451 LIST_ADDQ(&rs->rules, &chk->list);
7452
7453 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02007454 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
7455 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007456 if (!chk) {
7457 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
7458 goto error;
7459 }
7460 chk->expect.custom = tcpcheck_agent_expect_reply;
7461 chk->index = 1;
7462 LIST_ADDQ(&rs->rules, &chk->list);
7463
Christopher Fauletd7cee712020-04-21 13:45:00 +02007464 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007465
7466 ruleset_found:
7467 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007468 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007469 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007470
7471 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007472 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007473
7474 error:
7475 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02007476 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007477 err_code |= ERR_ALERT | ERR_FATAL;
7478 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007479}
7480
7481/* Parse the "agent-inter" server keyword */
7482static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7483 char **errmsg)
7484{
7485 const char *err = NULL;
7486 unsigned int delay;
7487 int err_code = 0;
7488
7489 if (!*(args[*cur_arg+1])) {
7490 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7491 goto error;
7492 }
7493
7494 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7495 if (err == PARSE_TIME_OVER) {
7496 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7497 args[*cur_arg+1], args[*cur_arg], srv->id);
7498 goto error;
7499 }
7500 else if (err == PARSE_TIME_UNDER) {
7501 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7502 args[*cur_arg+1], args[*cur_arg], srv->id);
7503 goto error;
7504 }
7505 else if (err) {
7506 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7507 *err, srv->id);
7508 goto error;
7509 }
7510 if (delay <= 0) {
7511 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7512 delay, args[*cur_arg], srv->id);
7513 goto error;
7514 }
7515 srv->agent.inter = delay;
7516
7517 out:
7518 return err_code;
7519
7520 error:
7521 err_code |= ERR_ALERT | ERR_FATAL;
7522 goto out;
7523}
7524
7525/* Parse the "agent-port" server keyword */
7526static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7527 char **errmsg)
7528{
7529 int err_code = 0;
7530
7531 if (!*(args[*cur_arg+1])) {
7532 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7533 goto error;
7534 }
7535
7536 global.maxsock++;
7537 srv->agent.port = atol(args[*cur_arg+1]);
7538
7539 out:
7540 return err_code;
7541
7542 error:
7543 err_code |= ERR_ALERT | ERR_FATAL;
7544 goto out;
7545}
7546
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007547int set_srv_agent_send(struct server *srv, const char *send)
7548{
7549 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7550 struct tcpcheck_var *var = NULL;
7551 char *str;
7552
7553 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007554 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007555 if (str == NULL || var == NULL)
7556 goto error;
7557
7558 free_tcpcheck_vars(&rules->preset_vars);
7559
7560 var->data.type = SMP_T_STR;
7561 var->data.u.str.area = str;
7562 var->data.u.str.data = strlen(str);
7563 LIST_INIT(&var->list);
7564 LIST_ADDQ(&rules->preset_vars, &var->list);
7565
7566 return 1;
7567
7568 error:
7569 free(str);
7570 free(var);
7571 return 0;
7572}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007573
7574/* Parse the "agent-send" server keyword */
7575static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7576 char **errmsg)
7577{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007578 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007579 int err_code = 0;
7580
7581 if (!*(args[*cur_arg+1])) {
7582 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7583 goto error;
7584 }
7585
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007586 if (!rules) {
7587 rules = calloc(1, sizeof(*rules));
7588 if (!rules) {
7589 memprintf(errmsg, "out of memory.");
7590 goto error;
7591 }
7592 LIST_INIT(&rules->preset_vars);
7593 srv->agent.tcpcheck_rules = rules;
7594 }
7595
7596 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007597 memprintf(errmsg, "out of memory.");
7598 goto error;
7599 }
7600
7601 out:
7602 return err_code;
7603
7604 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007605 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007606 err_code |= ERR_ALERT | ERR_FATAL;
7607 goto out;
7608}
7609
7610/* Parse the "no-agent-send" server keyword */
7611static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7612 char **errmsg)
7613{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007614 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007615 return 0;
7616}
7617
Christopher Fauletce8111e2020-04-06 15:04:11 +02007618/* Parse the "check" server keyword */
7619static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7620 char **errmsg)
7621{
7622 srv->do_check = 1;
7623 return 0;
7624}
7625
7626/* Parse the "check-send-proxy" server keyword */
7627static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7628 char **errmsg)
7629{
7630 srv->check.send_proxy = 1;
7631 return 0;
7632}
7633
7634/* Parse the "check-via-socks4" server keyword */
7635static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7636 char **errmsg)
7637{
7638 srv->check.via_socks4 = 1;
7639 return 0;
7640}
7641
7642/* Parse the "no-check" server keyword */
7643static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7644 char **errmsg)
7645{
7646 deinit_srv_check(srv);
7647 return 0;
7648}
7649
7650/* Parse the "no-check-send-proxy" server keyword */
7651static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7652 char **errmsg)
7653{
7654 srv->check.send_proxy = 0;
7655 return 0;
7656}
7657
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007658/* parse the "check-proto" server keyword */
7659static int srv_parse_check_proto(char **args, int *cur_arg,
7660 struct proxy *px, struct server *newsrv, char **err)
7661{
7662 int err_code = 0;
7663
7664 if (!*args[*cur_arg + 1]) {
7665 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7666 goto error;
7667 }
7668 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7669 if (!newsrv->check.mux_proto) {
7670 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7671 goto error;
7672 }
7673
7674 out:
7675 return err_code;
7676
7677 error:
7678 err_code |= ERR_ALERT | ERR_FATAL;
7679 goto out;
7680}
7681
7682
Christopher Fauletce8111e2020-04-06 15:04:11 +02007683/* Parse the "rise" server keyword */
7684static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7685 char **errmsg)
7686{
7687 int err_code = 0;
7688
7689 if (!*args[*cur_arg + 1]) {
7690 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7691 goto error;
7692 }
7693
7694 srv->check.rise = atol(args[*cur_arg+1]);
7695 if (srv->check.rise <= 0) {
7696 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7697 goto error;
7698 }
7699
7700 if (srv->check.health)
7701 srv->check.health = srv->check.rise;
7702
7703 out:
7704 return err_code;
7705
7706 error:
7707 deinit_srv_agent_check(srv);
7708 err_code |= ERR_ALERT | ERR_FATAL;
7709 goto out;
7710 return 0;
7711}
7712
7713/* Parse the "fall" server keyword */
7714static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7715 char **errmsg)
7716{
7717 int err_code = 0;
7718
7719 if (!*args[*cur_arg + 1]) {
7720 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7721 goto error;
7722 }
7723
7724 srv->check.fall = atol(args[*cur_arg+1]);
7725 if (srv->check.fall <= 0) {
7726 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7727 goto error;
7728 }
7729
7730 out:
7731 return err_code;
7732
7733 error:
7734 deinit_srv_agent_check(srv);
7735 err_code |= ERR_ALERT | ERR_FATAL;
7736 goto out;
7737 return 0;
7738}
7739
7740/* Parse the "inter" server keyword */
7741static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7742 char **errmsg)
7743{
7744 const char *err = NULL;
7745 unsigned int delay;
7746 int err_code = 0;
7747
7748 if (!*(args[*cur_arg+1])) {
7749 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7750 goto error;
7751 }
7752
7753 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7754 if (err == PARSE_TIME_OVER) {
7755 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7756 args[*cur_arg+1], args[*cur_arg], srv->id);
7757 goto error;
7758 }
7759 else if (err == PARSE_TIME_UNDER) {
7760 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7761 args[*cur_arg+1], args[*cur_arg], srv->id);
7762 goto error;
7763 }
7764 else if (err) {
7765 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7766 *err, srv->id);
7767 goto error;
7768 }
7769 if (delay <= 0) {
7770 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7771 delay, args[*cur_arg], srv->id);
7772 goto error;
7773 }
7774 srv->check.inter = delay;
7775
7776 out:
7777 return err_code;
7778
7779 error:
7780 err_code |= ERR_ALERT | ERR_FATAL;
7781 goto out;
7782}
7783
7784
7785/* Parse the "fastinter" server keyword */
7786static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7787 char **errmsg)
7788{
7789 const char *err = NULL;
7790 unsigned int delay;
7791 int err_code = 0;
7792
7793 if (!*(args[*cur_arg+1])) {
7794 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7795 goto error;
7796 }
7797
7798 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7799 if (err == PARSE_TIME_OVER) {
7800 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7801 args[*cur_arg+1], args[*cur_arg], srv->id);
7802 goto error;
7803 }
7804 else if (err == PARSE_TIME_UNDER) {
7805 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7806 args[*cur_arg+1], args[*cur_arg], srv->id);
7807 goto error;
7808 }
7809 else if (err) {
7810 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7811 *err, srv->id);
7812 goto error;
7813 }
7814 if (delay <= 0) {
7815 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7816 delay, args[*cur_arg], srv->id);
7817 goto error;
7818 }
7819 srv->check.fastinter = delay;
7820
7821 out:
7822 return err_code;
7823
7824 error:
7825 err_code |= ERR_ALERT | ERR_FATAL;
7826 goto out;
7827}
7828
7829
7830/* Parse the "downinter" server keyword */
7831static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7832 char **errmsg)
7833{
7834 const char *err = NULL;
7835 unsigned int delay;
7836 int err_code = 0;
7837
7838 if (!*(args[*cur_arg+1])) {
7839 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7840 goto error;
7841 }
7842
7843 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7844 if (err == PARSE_TIME_OVER) {
7845 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7846 args[*cur_arg+1], args[*cur_arg], srv->id);
7847 goto error;
7848 }
7849 else if (err == PARSE_TIME_UNDER) {
7850 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7851 args[*cur_arg+1], args[*cur_arg], srv->id);
7852 goto error;
7853 }
7854 else if (err) {
7855 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7856 *err, srv->id);
7857 goto error;
7858 }
7859 if (delay <= 0) {
7860 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7861 delay, args[*cur_arg], srv->id);
7862 goto error;
7863 }
7864 srv->check.downinter = delay;
7865
7866 out:
7867 return err_code;
7868
7869 error:
7870 err_code |= ERR_ALERT | ERR_FATAL;
7871 goto out;
7872}
7873
7874/* Parse the "port" server keyword */
7875static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7876 char **errmsg)
7877{
7878 int err_code = 0;
7879
7880 if (!*(args[*cur_arg+1])) {
7881 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7882 goto error;
7883 }
7884
7885 global.maxsock++;
7886 srv->check.port = atol(args[*cur_arg+1]);
7887 srv->flags |= SRV_F_CHECKPORT;
7888
7889 out:
7890 return err_code;
7891
7892 error:
7893 err_code |= ERR_ALERT | ERR_FATAL;
7894 goto out;
7895}
7896
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007897static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007898 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7899 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7900 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007901 { 0, NULL, NULL },
7902}};
7903
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007904static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007905 { "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 +02007906 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7907 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7908 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7909 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7910 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007911 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007912 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007913 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7914 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007915 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007916 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7917 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7918 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7919 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7920 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7921 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7922 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7923 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007924 { NULL, NULL, 0 },
7925}};
7926
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007927INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007928INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007929
Willy Tarreaubd741542010-03-16 18:46:54 +01007930/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007931 * Local variables:
7932 * c-indent-level: 8
7933 * c-basic-offset: 8
7934 * End:
7935 */