blob: 7c75a1d84290e015b50f678bab92fbd9189d0a89 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
Christopher Faulet733dd732020-04-28 10:24:23 +02001205 if (b_data(&check->bi) < offset+4) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001659 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1660 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001686 * addr if specified on the server. otherwise, use the server addr (it
1687 * MUST exist at this step).
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001688 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001689 *conn->dst = (is_addr(&connect->addr)
1690 ? connect->addr
1691 : (is_addr(&check->addr) ? check->addr : s->addr));
1692 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001693
Christopher Faulet61cc8522020-04-20 14:54:42 +02001694 port = 0;
1695 if (!port && connect->port)
1696 port = connect->port;
1697 if (!port && connect->port_expr) {
1698 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001699
Christopher Faulet61cc8522020-04-20 14:54:42 +02001700 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1701 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1702 connect->port_expr, SMP_T_SINT);
1703 if (smp)
1704 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001705 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001706 if (!port && is_inet_addr(&connect->addr))
1707 port = get_host_port(&connect->addr);
1708 if (!port && check->port)
1709 port = check->port;
1710 if (!port && is_inet_addr(&check->addr))
1711 port = get_host_port(&check->addr);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001712 if (!port) {
1713 /* The server MUST exist here */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001714 port = s->svc_port;
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001715 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001716 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001717
Christopher Faulet61cc8522020-04-20 14:54:42 +02001718 xprt = ((connect->options & TCPCHK_OPT_SSL)
1719 ? xprt_get(XPRT_SSL)
1720 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 conn_prepare(conn, proto, xprt);
1723 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001724
Christopher Faulet61cc8522020-04-20 14:54:42 +02001725 status = SF_ERR_INTERNAL;
1726 if (proto && proto->connect) {
1727 struct tcpcheck_rule *next;
1728 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1731 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001732
Christopher Faulet61cc8522020-04-20 14:54:42 +02001733 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1734 if (!next || next->action != TCPCHK_ACT_EXPECT)
1735 flags |= CONNECT_DELACK_ALWAYS;
1736 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001737 }
1738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 if (status != SF_ERR_NONE)
1740 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 conn->flags |= CO_FL_PRIVATE;
1743 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001744
Christopher Faulet61cc8522020-04-20 14:54:42 +02001745 /* The mux may be initialized now if there isn't server attached to the
1746 * check (email alerts) or if there is a mux proto specified or if there
1747 * is no alpn.
1748 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001749 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1750 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001751 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001752
Christopher Faulet61cc8522020-04-20 14:54:42 +02001753 if (connect->mux_proto)
1754 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001755 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001756 mux_ops = check->mux_proto->mux;
1757 else {
1758 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1759 ? PROTO_MODE_HTTP
1760 : PROTO_MODE_TCP);
1761
1762 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001763 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001764 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1765 status = SF_ERR_INTERNAL;
1766 goto fail_check;
1767 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001768 }
1769
Christopher Faulet61cc8522020-04-20 14:54:42 +02001770#ifdef USE_OPENSSL
1771 if (connect->sni)
1772 ssl_sock_set_servername(conn, connect->sni);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001773 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001774 ssl_sock_set_servername(conn, s->check.sni);
1775
1776 if (connect->alpn)
1777 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001778 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001779 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1780#endif
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001781 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001782 conn->send_proxy_ofs = 1;
1783 conn->flags |= CO_FL_SOCKS4;
1784 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001785 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001786 conn->send_proxy_ofs = 1;
1787 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001788 }
1789
Christopher Faulet61cc8522020-04-20 14:54:42 +02001790 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1791 conn->send_proxy_ofs = 1;
1792 conn->flags |= CO_FL_SEND_PROXY;
1793 }
Christopher Faulet931ae5b2020-04-28 10:31:53 +02001794 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001795 conn->send_proxy_ofs = 1;
1796 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001797 }
1798
Christopher Faulet61cc8522020-04-20 14:54:42 +02001799 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1800 /* Some servers don't like reset on close */
1801 fdtab[cs->conn->handle.fd].linger_risk = 0;
1802 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001803
Christopher Faulet61cc8522020-04-20 14:54:42 +02001804 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1805 if (xprt_add_hs(conn) < 0)
1806 status = SF_ERR_RESOURCE;
1807 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001808
Christopher Faulet61cc8522020-04-20 14:54:42 +02001809 fail_check:
1810 /* It can return one of :
1811 * - SF_ERR_NONE if everything's OK
1812 * - SF_ERR_SRVTO if there are no more servers
1813 * - SF_ERR_SRVCL if the connection was refused by the server
1814 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1815 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1816 * - SF_ERR_INTERNAL for any other purely internal errors
1817 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1818 * Note that we try to prevent the network stack from sending the ACK during the
1819 * connect() when a pure TCP check is used (without PROXY protocol).
1820 */
1821 switch (status) {
1822 case SF_ERR_NONE:
1823 /* we allow up to min(inter, timeout.connect) for a connection
1824 * to establish but only when timeout.check is set as it may be
1825 * to short for a full check otherwise
1826 */
1827 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001828
Christopher Faulet61cc8522020-04-20 14:54:42 +02001829 if (proxy->timeout.check && proxy->timeout.connect) {
1830 int t_con = tick_add(now_ms, proxy->timeout.connect);
1831 t->expire = tick_first(t->expire, t_con);
1832 }
1833 break;
1834 case SF_ERR_SRVTO: /* ETIMEDOUT */
1835 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1836 case SF_ERR_PRXCOND:
1837 case SF_ERR_RESOURCE:
1838 case SF_ERR_INTERNAL:
1839 chk_report_conn_err(check, errno, 0);
1840 ret = TCPCHK_EVAL_STOP;
1841 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001842 }
1843
Christopher Faulet61cc8522020-04-20 14:54:42 +02001844 /* don't do anything until the connection is established */
1845 if (conn->flags & CO_FL_WAIT_XPRT) {
1846 ret = TCPCHK_EVAL_WAIT;
1847 goto out;
1848 }
1849
1850 out:
1851 if (conn && check->result == CHK_RES_FAILED)
1852 conn->flags |= CO_FL_ERROR;
1853 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001854}
1855
Christopher Faulet61cc8522020-04-20 14:54:42 +02001856/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1857 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1858 * TCPCHK_EVAL_STOP if an error occurred.
1859 */
1860static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001861{
1862 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001863 struct tcpcheck_send *send = &rule->send;
1864 struct conn_stream *cs = check->cs;
1865 struct connection *conn = cs_conn(cs);
1866 struct buffer *tmp = NULL;
1867 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001868
Christopher Faulet61cc8522020-04-20 14:54:42 +02001869 /* reset the read & write buffer */
1870 b_reset(&check->bi);
1871 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001872
Christopher Faulet61cc8522020-04-20 14:54:42 +02001873 switch (send->type) {
1874 case TCPCHK_SEND_STRING:
1875 case TCPCHK_SEND_BINARY:
1876 if (istlen(send->data) >= b_size(&check->bo)) {
1877 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1878 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1879 tcpcheck_get_step_id(check, rule));
1880 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1881 ret = TCPCHK_EVAL_STOP;
1882 goto out;
1883 }
1884 b_putist(&check->bo, send->data);
1885 break;
1886 case TCPCHK_SEND_STRING_LF:
1887 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1888 if (!b_data(&check->bo))
1889 goto out;
1890 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001891 case TCPCHK_SEND_BINARY_LF: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02001892 int len = b_size(&check->bo);
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001893
Christopher Faulet61cc8522020-04-20 14:54:42 +02001894 tmp = alloc_trash_chunk();
1895 if (!tmp)
1896 goto error_lf;
1897 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1898 if (!b_data(tmp))
1899 goto out;
1900 tmp->area[tmp->data] = '\0';
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001901 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001902 goto error_lf;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001903 check->bo.data = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001904 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02001905 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001906 case TCPCHK_SEND_HTTP: {
1907 struct htx_sl *sl;
1908 struct ist meth, uri, vsn, clen, body;
1909 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001910
Christopher Faulet61cc8522020-04-20 14:54:42 +02001911 tmp = alloc_trash_chunk();
1912 if (!tmp)
1913 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001914
Christopher Faulet61cc8522020-04-20 14:54:42 +02001915 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1916 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1917 : http_known_methods[send->http.meth.meth]);
1918 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1919 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001920
Christopher Faulet8bf8fda2020-04-28 09:10:19 +02001921 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1922 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001923 slflags |= HTX_SL_F_VER_11;
1924 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1925 if (!isttest(send->http.body))
1926 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001927
Christopher Faulet61cc8522020-04-20 14:54:42 +02001928 htx = htx_from_buf(&check->bo);
1929 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1930 if (!sl)
1931 goto error_htx;
1932 sl->info.req.meth = send->http.meth.meth;
Christopher Faulet39959a72020-04-29 13:21:37 +02001933 if (!http_update_host(htx, sl, uri))
1934 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001935
Christopher Faulet61cc8522020-04-20 14:54:42 +02001936 body = send->http.body; // TODO: handle body_fmt
1937 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001938
Christopher Faulet61cc8522020-04-20 14:54:42 +02001939 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1940 !htx_add_header(htx, ist("Content-length"), clen))
1941 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001942
Christopher Faulet61cc8522020-04-20 14:54:42 +02001943 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1944 struct tcpcheck_http_hdr *hdr;
Christopher Faulet39959a72020-04-29 13:21:37 +02001945 struct ist hdr_value;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001946
Christopher Faulet61cc8522020-04-20 14:54:42 +02001947 list_for_each_entry(hdr, &send->http.hdrs, list) {
1948 chunk_reset(tmp);
1949 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1950 if (!b_data(tmp))
1951 continue;
Christopher Faulet39959a72020-04-29 13:21:37 +02001952 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1953 if (!htx_add_header(htx, hdr->name, hdr_value))
Christopher Faulet61cc8522020-04-20 14:54:42 +02001954 goto error_htx;
Christopher Faulet39959a72020-04-29 13:21:37 +02001955 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1956 if (!http_update_authority(htx, sl, hdr_value))
1957 goto error_htx;
1958 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001959 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001960
Christopher Faulet61cc8522020-04-20 14:54:42 +02001961 }
1962 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1963 chunk_reset(tmp);
1964 httpchk_build_status_header(check->server, tmp);
1965 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1966 goto error_htx;
1967 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001968
Christopher Faulet61cc8522020-04-20 14:54:42 +02001969 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1970 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1971 !htx_add_endof(htx, HTX_BLK_EOM))
1972 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001973
Christopher Faulet61cc8522020-04-20 14:54:42 +02001974 htx_to_buf(htx, &check->bo);
1975 break;
1976 }
1977 case TCPCHK_SEND_UNDEF:
1978 /* Should never happen. */
1979 ret = TCPCHK_EVAL_STOP;
1980 goto out;
1981 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001982
Christopher Faulet6d471212020-04-22 11:09:25 +02001983
1984 if (conn->mux->snd_buf(cs, &check->bo,
1985 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001986 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001988 goto out;
1989 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001990 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001991 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001992 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1993 ret = TCPCHK_EVAL_WAIT;
1994 goto out;
1995 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001996
Christopher Faulet61cc8522020-04-20 14:54:42 +02001997 out:
1998 free_trash_chunk(tmp);
1999 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002000
Christopher Faulet61cc8522020-04-20 14:54:42 +02002001 error_htx:
2002 if (htx) {
2003 htx_reset(htx);
2004 htx_to_buf(htx, &check->bo);
2005 }
2006 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
2007 tcpcheck_get_step_id(check, rule));
2008 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2009 ret = TCPCHK_EVAL_STOP;
2010 goto out;
2011
2012 error_lf:
2013 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2014 tcpcheck_get_step_id(check, rule));
2015 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2016 ret = TCPCHK_EVAL_STOP;
2017 goto out;
2018
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002019}
2020
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2022 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2023 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2024 * TCPCHK_EVAL_STOP if an error occurred.
2025 */
2026static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002027{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002028 struct conn_stream *cs = check->cs;
2029 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 size_t max, read, cur_read = 0;
2032 int is_empty;
2033 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002034
Christopher Faulet61cc8522020-04-20 14:54:42 +02002035 if (check->wait_list.events & SUB_RETRY_RECV)
2036 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002037
Christopher Faulet61cc8522020-04-20 14:54:42 +02002038 if (cs->flags & CS_FL_EOS)
2039 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002040
Christopher Faulet61cc8522020-04-20 14:54:42 +02002041 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002042
Christopher Faulet61cc8522020-04-20 14:54:42 +02002043 /* prepare to detect if the mux needs more room */
2044 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002045
Christopher Faulet61cc8522020-04-20 14:54:42 +02002046 while ((cs->flags & CS_FL_RCV_MORE) ||
2047 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2048 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2049 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2050 cur_read += read;
2051 if (!read ||
2052 (cs->flags & CS_FL_WANT_ROOM) ||
2053 (--read_poll <= 0) ||
2054 (read < max && read >= global.tune.recv_enough))
2055 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002056 }
2057
Christopher Faulet61cc8522020-04-20 14:54:42 +02002058 end_recv:
2059 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2060 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2061 /* Report network errors only if we got no other data. Otherwise
2062 * we'll let the upper layers decide whether the response is OK
2063 * or not. It is very common that an RST sent by the server is
2064 * reported as an error just after the last data chunk.
2065 */
2066 goto stop;
2067 }
2068 if (!cur_read) {
2069 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2070 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2071 goto wait_more_data;
2072 }
2073 if (is_empty) {
2074 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2075 tcpcheck_get_step_id(check, rule));
2076 if (rule->comment)
2077 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2078 set_server_check_status(check, rule->expect.err_status, trash.area);
2079 goto stop;
2080 }
2081 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002082
2083 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002084 return ret;
2085
Christopher Faulet61cc8522020-04-20 14:54:42 +02002086 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002087 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002088 goto out;
2089
2090 wait_more_data:
2091 ret = TCPCHK_EVAL_WAIT;
2092 goto out;
2093}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002094
Christopher Faulet61cc8522020-04-20 14:54:42 +02002095/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2096 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2097 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2098 * error occurred.
2099 */
2100static 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 +02002101{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002102 struct htx *htx = htxbuf(&check->bi);
2103 struct htx_sl *sl;
2104 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002105 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002106 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002107 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002108 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002109 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002110 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002111
Christopher Faulet61cc8522020-04-20 14:54:42 +02002112 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002113
Christopher Faulet61cc8522020-04-20 14:54:42 +02002114 if (htx->flags & HTX_FL_PARSING_ERROR) {
2115 status = HCHK_STATUS_L7RSP;
2116 goto error;
2117 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002118
Christopher Faulet61cc8522020-04-20 14:54:42 +02002119 if (htx_is_empty(htx)) {
2120 if (last_read) {
2121 status = HCHK_STATUS_L7RSP;
2122 goto error;
2123 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002124 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002125 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002126
Christopher Faulet61cc8522020-04-20 14:54:42 +02002127 sl = http_get_stline(htx);
2128 check->code = sl->info.res.status;
2129
2130 if (check->server &&
2131 (check->server->proxy->options & PR_O_DISABLE404) &&
2132 (check->server->next_state != SRV_ST_STOPPED) &&
2133 (check->code == 404)) {
2134 /* 404 may be accepted as "stopping" only if the server was up */
2135 goto out;
2136 }
2137
2138 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2139 /* Make GCC happy ; initialize match to a failure state. */
2140 match = inverse;
2141
2142 switch (expect->type) {
2143 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002144 match = 0;
2145 for (i = 0; i < expect->codes.num; i++) {
2146 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2147 sl->info.res.status <= expect->codes.codes[i][1]) {
2148 match = 1;
2149 break;
2150 }
2151 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002152
2153 /* Set status and description in case of error */
2154 status = HCHK_STATUS_L7STS;
2155 desc = htx_sl_res_reason(sl);
2156 break;
2157 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2158 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2159
2160 /* Set status and description in case of error */
2161 status = HCHK_STATUS_L7STS;
2162 desc = htx_sl_res_reason(sl);
2163 break;
2164
2165 case TCPCHK_EXPECT_HTTP_BODY:
2166 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2167 chunk_reset(&trash);
2168 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2169 enum htx_blk_type type = htx_get_blk_type(blk);
2170
2171 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2172 break;
2173 if (type == HTX_BLK_DATA) {
2174 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2175 break;
2176 }
2177 }
2178
2179 if (!b_data(&trash)) {
2180 if (!last_read)
2181 goto wait_more_data;
2182 status = HCHK_STATUS_L7RSP;
2183 desc = ist("HTTP content check could not find a response body");
2184 goto error;
2185 }
2186
2187 if (!last_read &&
2188 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2189 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2190 ret = TCPCHK_EVAL_WAIT;
2191 goto out;
2192 }
2193
2194 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002195 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002196 else
2197 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2198
2199 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002200 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002201 desc = (inverse
2202 ? ist("HTTP check matched unwanted content")
2203 : ist("HTTP content check did not match"));
2204 break;
2205
2206 default:
2207 /* should never happen */
2208 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002209 goto error;
2210 }
2211
Christopher Faulet61cc8522020-04-20 14:54:42 +02002212 /* Wait for more data on mismatch only if no minimum is defined (-1),
2213 * otherwise the absence of match is already conclusive.
2214 */
2215 if (!match && !last_read && (expect->min_recv == -1)) {
2216 ret = TCPCHK_EVAL_WAIT;
2217 goto out;
2218 }
2219
2220 if (!(match ^ inverse))
2221 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002222
2223 out:
2224 free_trash_chunk(msg);
2225 return ret;
2226
2227 error:
2228 ret = TCPCHK_EVAL_STOP;
2229 msg = alloc_trash_chunk();
2230 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002231 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002232 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2233 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002234
2235 wait_more_data:
2236 ret = TCPCHK_EVAL_WAIT;
2237 goto out;
2238}
2239
Christopher Faulet61cc8522020-04-20 14:54:42 +02002240/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2241 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2242 * if an error occurred.
2243 */
2244static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2245{
2246 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2247 struct tcpcheck_expect *expect = &rule->expect;
2248 struct buffer *msg = NULL;
2249 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002250
Christopher Faulet61cc8522020-04-20 14:54:42 +02002251 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002252
Christopher Faulet61cc8522020-04-20 14:54:42 +02002253 /* The current expect might need more data than the previous one, check again
2254 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002255 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002256 if (!last_read) {
2257 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2258 (b_data(&check->bi) < istlen(expect->data))) {
2259 ret = TCPCHK_EVAL_WAIT;
2260 goto out;
2261 }
2262 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2263 ret = TCPCHK_EVAL_WAIT;
2264 goto out;
2265 }
2266 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002267
Christopher Faulet61cc8522020-04-20 14:54:42 +02002268 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2269 /* Make GCC happy ; initialize match to a failure state. */
2270 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002271
Christopher Faulet61cc8522020-04-20 14:54:42 +02002272 switch (expect->type) {
2273 case TCPCHK_EXPECT_STRING:
2274 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002275 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 +02002276 break;
2277 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002278 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 +02002279 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002280
Christopher Faulet61cc8522020-04-20 14:54:42 +02002281 case TCPCHK_EXPECT_REGEX_BINARY:
2282 chunk_reset(&trash);
2283 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002284 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002285 break;
2286 case TCPCHK_EXPECT_CUSTOM:
2287 if (expect->custom)
2288 ret = expect->custom(check, rule, last_read);
2289 goto out;
2290 default:
2291 /* Should never happen. */
2292 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002293 goto out;
2294 }
2295
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002296
Christopher Faulet61cc8522020-04-20 14:54:42 +02002297 /* Wait for more data on mismatch only if no minimum is defined (-1),
2298 * otherwise the absence of match is already conclusive.
2299 */
2300 if (!match && !last_read && (expect->min_recv == -1)) {
2301 ret = TCPCHK_EVAL_WAIT;
2302 goto out;
2303 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002304
Christopher Faulet61cc8522020-04-20 14:54:42 +02002305 /* Result as expected, next rule. */
2306 if (match ^ inverse)
2307 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002308
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002309
Christopher Faulet61cc8522020-04-20 14:54:42 +02002310 /* From this point on, we matched something we did not want, this is an error state. */
2311 ret = TCPCHK_EVAL_STOP;
2312 msg = alloc_trash_chunk();
2313 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002314 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002315 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2316 free_trash_chunk(msg);
2317 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002318
Christopher Faulet61cc8522020-04-20 14:54:42 +02002319 out:
2320 return ret;
2321}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002322
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2324 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2325 * waits.
2326 */
2327static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2328{
2329 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2330 struct act_rule *act_rule;
2331 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002332
Christopher Faulet61cc8522020-04-20 14:54:42 +02002333 act_rule =rule->action_kw.rule;
2334 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2335 if (act_ret != ACT_RET_CONT) {
2336 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2337 tcpcheck_get_step_id(check, rule));
2338 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2339 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002340 }
2341
Christopher Faulet61cc8522020-04-20 14:54:42 +02002342 return ret;
2343}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002344
Christopher Faulet61cc8522020-04-20 14:54:42 +02002345/* Executes a tcp-check ruleset. Note that this is called both from the
2346 * connection's wake() callback and from the check scheduling task. It returns
2347 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2348 * presenting the risk of an fd replacement.
2349 *
2350 * Please do NOT place any return statement in this function and only leave
2351 * via the out_end_tcpcheck label after setting retcode.
2352 */
2353static int tcpcheck_main(struct check *check)
2354{
2355 struct tcpcheck_rule *rule;
2356 struct conn_stream *cs = check->cs;
2357 struct connection *conn = cs_conn(cs);
2358 int must_read = 1, last_read = 0;
2359 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002360
Christopher Faulet61cc8522020-04-20 14:54:42 +02002361 /* here, we know that the check is complete or that it failed */
2362 if (check->result != CHK_RES_UNKNOWN)
Christopher Fauletb693a0d2020-04-27 15:59:22 +02002363 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002364
Christopher Faulet61cc8522020-04-20 14:54:42 +02002365 /* 1- check for connection error, if any */
2366 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2367 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002368
Christopher Faulet61cc8522020-04-20 14:54:42 +02002369 /* 2- check if we are waiting for the connection establishment. It only
2370 * happens during TCPCHK_ACT_CONNECT. */
2371 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2372 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2373 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2374 if (rule->action == TCPCHK_ACT_SEND)
2375 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2376 else if (rule->action == TCPCHK_ACT_EXPECT)
2377 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2378 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002379 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002380 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002381
2382 /* 3- check for pending outgoing data. It only happens during
2383 * TCPCHK_ACT_SEND. */
2384 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2385 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002386 ret = conn->mux->snd_buf(cs, &check->bo,
2387 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002388 if (ret <= 0) {
2389 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2390 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002391 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002392 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002393 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2394 goto out;
2395 }
2396 }
2397 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002398 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002399
Christopher Faulet61cc8522020-04-20 14:54:42 +02002400 /* 4- check if a rule must be resume. It happens if check->current_step
2401 * is defined. */
2402 else if (check->current_step)
2403 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002404
Christopher Faulet61cc8522020-04-20 14:54:42 +02002405 /* 5- It is the first evaluation. We must create a session and preset
2406 * tcp-check variables */
2407 else {
2408 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002409
Christopher Faulet61cc8522020-04-20 14:54:42 +02002410 /* First evaluation, create a session */
2411 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2412 if (!check->sess) {
2413 chunk_printf(&trash, "TCPCHK error allocating check session");
2414 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2415 goto out_end_tcpcheck;
2416 }
2417 vars_init(&check->vars, SCOPE_CHECK);
2418 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002419
Christopher Faulet61cc8522020-04-20 14:54:42 +02002420 /* Preset tcp-check variables */
2421 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2422 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002423
Christopher Faulet61cc8522020-04-20 14:54:42 +02002424 memset(&smp, 0, sizeof(smp));
2425 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2426 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002427 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002428 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002429 }
2430
Christopher Faulet61cc8522020-04-20 14:54:42 +02002431 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002432
Christopher Faulet61cc8522020-04-20 14:54:42 +02002433 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2434 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002435
Christopher Faulet61cc8522020-04-20 14:54:42 +02002436 check->code = 0;
2437 switch (rule->action) {
2438 case TCPCHK_ACT_CONNECT:
2439 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002440
Christopher Faulet61cc8522020-04-20 14:54:42 +02002441 /* close but not release yet previous connection */
2442 if (check->cs) {
2443 cs_close(check->cs);
2444 retcode = -1; /* do not reuse the fd in the caller! */
2445 }
2446 eval_ret = tcpcheck_eval_connect(check, rule);
2447 must_read = 1; last_read = 0;
2448 break;
2449 case TCPCHK_ACT_SEND:
2450 check->current_step = rule;
2451 eval_ret = tcpcheck_eval_send(check, rule);
2452 must_read = 1;
2453 break;
2454 case TCPCHK_ACT_EXPECT:
2455 check->current_step = rule;
2456 if (must_read) {
2457 if (check->proxy->timeout.check)
2458 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002459
Christopher Faulet61cc8522020-04-20 14:54:42 +02002460 eval_ret = tcpcheck_eval_recv(check, rule);
2461 if (eval_ret == TCPCHK_EVAL_STOP)
2462 goto out_end_tcpcheck;
2463 else if (eval_ret == TCPCHK_EVAL_WAIT)
2464 goto out;
2465 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2466 must_read = 0;
2467 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002468
Christopher Faulet61cc8522020-04-20 14:54:42 +02002469 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2470 ? tcpcheck_eval_expect_http(check, rule, last_read)
2471 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002472
Christopher Faulet61cc8522020-04-20 14:54:42 +02002473 if (eval_ret == TCPCHK_EVAL_WAIT) {
2474 check->current_step = rule->expect.head;
2475 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2476 }
2477 break;
2478 case TCPCHK_ACT_ACTION_KW:
2479 /* Don't update the current step */
2480 eval_ret = tcpcheck_eval_action_kw(check, rule);
2481 break;
2482 default:
2483 /* Otherwise, just go to the next one and don't update
2484 * the current step
2485 */
2486 eval_ret = TCPCHK_EVAL_CONTINUE;
2487 break;
2488 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002489
Christopher Faulet61cc8522020-04-20 14:54:42 +02002490 switch (eval_ret) {
2491 case TCPCHK_EVAL_CONTINUE:
2492 break;
2493 case TCPCHK_EVAL_WAIT:
2494 goto out;
2495 case TCPCHK_EVAL_STOP:
2496 goto out_end_tcpcheck;
2497 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002498 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002499
Christopher Faulet61cc8522020-04-20 14:54:42 +02002500 /* All rules was evaluated */
2501 if (check->current_step) {
2502 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002503
Christopher Faulet61cc8522020-04-20 14:54:42 +02002504 if (rule->action == TCPCHK_ACT_EXPECT) {
2505 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002506
Christopher Faulet61cc8522020-04-20 14:54:42 +02002507 if (check->server &&
2508 (check->server->proxy->options & PR_O_DISABLE404) &&
2509 (check->server->next_state != SRV_ST_STOPPED) &&
2510 (check->code == 404)) {
2511 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2512 goto out_end_tcpcheck;
2513 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002514
Christopher Faulet61cc8522020-04-20 14:54:42 +02002515 msg = alloc_trash_chunk();
2516 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002517 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002518 set_server_check_status(check, rule->expect.ok_status,
2519 (msg ? b_head(msg) : "(tcp-check)"));
2520 free_trash_chunk(msg);
2521 }
2522 else if (rule->action == TCPCHK_ACT_CONNECT) {
2523 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002524 enum healthcheck_status status = HCHK_STATUS_L4OK;
2525#ifdef USE_OPENSSL
2526 if (conn && ssl_sock_is_ssl(conn))
2527 status = HCHK_STATUS_L6OK;
2528#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002529 set_server_check_status(check, status, msg);
2530 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002532 else
2533 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002534
Christopher Faulet61cc8522020-04-20 14:54:42 +02002535 out_end_tcpcheck:
2536 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2537 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002538
Christopher Faulet61cc8522020-04-20 14:54:42 +02002539 out:
2540 return retcode;
2541}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002542
Christopher Faulet14cd3162020-04-16 14:50:06 +02002543
Christopher Faulet61cc8522020-04-20 14:54:42 +02002544/**************************************************************************/
2545/************** Health-checks based on an external process ****************/
2546/**************************************************************************/
2547static struct list pid_list = LIST_HEAD_INIT(pid_list);
2548static struct pool_head *pool_head_pid_list;
2549__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002550
Christopher Faulet61cc8522020-04-20 14:54:42 +02002551struct extcheck_env {
2552 char *name; /* environment variable name */
2553 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2554};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002555
Christopher Faulet61cc8522020-04-20 14:54:42 +02002556/* environment variables memory requirement for different types of data */
2557#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2558 * such environment variables are not updatable. */
2559#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2560#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2561#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002562
Christopher Faulet61cc8522020-04-20 14:54:42 +02002563/* external checks environment variables */
2564enum {
2565 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002566
Christopher Faulet61cc8522020-04-20 14:54:42 +02002567 /* Proxy specific environment variables */
2568 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2569 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2570 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2571 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002572
Christopher Faulet61cc8522020-04-20 14:54:42 +02002573 /* Server specific environment variables */
2574 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2575 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2576 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2577 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2578 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2579 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002580
Christopher Faulet61cc8522020-04-20 14:54:42 +02002581 EXTCHK_SIZE
2582};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002583
Christopher Faulet61cc8522020-04-20 14:54:42 +02002584const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2585 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2586 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2587 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2588 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2589 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2590 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2591 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2592 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2593 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2594 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2595 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2596};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002597
Christopher Faulet61cc8522020-04-20 14:54:42 +02002598void block_sigchld(void)
2599{
2600 sigset_t set;
2601 sigemptyset(&set);
2602 sigaddset(&set, SIGCHLD);
2603 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2604}
Willy Tarreaube373152018-09-06 11:45:30 +02002605
Christopher Faulet61cc8522020-04-20 14:54:42 +02002606void unblock_sigchld(void)
2607{
2608 sigset_t set;
2609 sigemptyset(&set);
2610 sigaddset(&set, SIGCHLD);
2611 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002612}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002613
Christopher Faulet61cc8522020-04-20 14:54:42 +02002614static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002615{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002616 struct pid_list *elem;
2617 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002618
Christopher Faulet61cc8522020-04-20 14:54:42 +02002619 elem = pool_alloc(pool_head_pid_list);
2620 if (!elem)
2621 return NULL;
2622 elem->pid = pid;
2623 elem->t = t;
2624 elem->exited = 0;
2625 check->curpid = elem;
2626 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002627
Christopher Faulet61cc8522020-04-20 14:54:42 +02002628 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2629 LIST_ADD(&pid_list, &elem->list);
2630 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632 return elem;
2633}
Christopher Faulete5870d82020-04-15 11:32:03 +02002634
Christopher Faulet61cc8522020-04-20 14:54:42 +02002635static void pid_list_del(struct pid_list *elem)
2636{
2637 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002638
Christopher Faulet61cc8522020-04-20 14:54:42 +02002639 if (!elem)
2640 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002641
Christopher Faulet61cc8522020-04-20 14:54:42 +02002642 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2643 LIST_DEL(&elem->list);
2644 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002645
Christopher Faulet61cc8522020-04-20 14:54:42 +02002646 if (!elem->exited)
2647 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002648
Christopher Faulet61cc8522020-04-20 14:54:42 +02002649 check = elem->t->context;
2650 check->curpid = NULL;
2651 pool_free(pool_head_pid_list, elem);
2652}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002653
Christopher Faulet61cc8522020-04-20 14:54:42 +02002654/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2655static void pid_list_expire(pid_t pid, int status)
2656{
2657 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002658
Christopher Faulet61cc8522020-04-20 14:54:42 +02002659 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2660 list_for_each_entry(elem, &pid_list, list) {
2661 if (elem->pid == pid) {
2662 elem->t->expire = now_ms;
2663 elem->status = status;
2664 elem->exited = 1;
2665 task_wakeup(elem->t, TASK_WOKEN_IO);
2666 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002667 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002668 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002669 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2670}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002671
Christopher Faulet61cc8522020-04-20 14:54:42 +02002672static void sigchld_handler(struct sig_handler *sh)
2673{
2674 pid_t pid;
2675 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002676
Christopher Faulet61cc8522020-04-20 14:54:42 +02002677 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2678 pid_list_expire(pid, status);
2679}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002680
Christopher Faulet61cc8522020-04-20 14:54:42 +02002681static int init_pid_list(void)
2682{
2683 if (pool_head_pid_list != NULL)
2684 /* Nothing to do */
2685 return 0;
2686
2687 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2688 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2689 strerror(errno));
2690 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002691 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002692
Christopher Faulet61cc8522020-04-20 14:54:42 +02002693 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2694 if (pool_head_pid_list == NULL) {
2695 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2696 strerror(errno));
2697 return 1;
2698 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002699
Christopher Faulet61cc8522020-04-20 14:54:42 +02002700 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002701}
2702
Christopher Faulet61cc8522020-04-20 14:54:42 +02002703/* helper macro to set an environment variable and jump to a specific label on failure. */
2704#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002705
Christopher Faulet61cc8522020-04-20 14:54:42 +02002706/*
2707 * helper function to allocate enough memory to store an environment variable.
2708 * It will also check that the environment variable is updatable, and silently
2709 * fail if not.
2710 */
2711static int extchk_setenv(struct check *check, int idx, const char *value)
2712{
2713 int len, ret;
2714 char *envname;
2715 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002716
Christopher Faulet61cc8522020-04-20 14:54:42 +02002717 if (idx < 0 || idx >= EXTCHK_SIZE) {
2718 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2719 return 1;
2720 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002721
Christopher Faulet61cc8522020-04-20 14:54:42 +02002722 envname = extcheck_envs[idx].name;
2723 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002724
Christopher Faulet61cc8522020-04-20 14:54:42 +02002725 /* Check if the environment variable is already set, and silently reject
2726 * the update if this one is not updatable. */
2727 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2728 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002729
Christopher Faulet61cc8522020-04-20 14:54:42 +02002730 /* Instead of sending NOT_USED, sending an empty value is preferable */
2731 if (strcmp(value, "NOT_USED") == 0) {
2732 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002733 }
2734
Christopher Faulet61cc8522020-04-20 14:54:42 +02002735 len = strlen(envname) + 1;
2736 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2737 len += strlen(value);
2738 else
2739 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002740
Christopher Faulet61cc8522020-04-20 14:54:42 +02002741 if (!check->envp[idx])
2742 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002743
Christopher Faulet61cc8522020-04-20 14:54:42 +02002744 if (!check->envp[idx]) {
2745 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2746 return 1;
2747 }
2748 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2749 if (ret < 0) {
2750 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2751 return 1;
2752 }
2753 else if (ret > len) {
2754 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2755 return 1;
2756 }
2757 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002758}
2759
Christopher Faulet61cc8522020-04-20 14:54:42 +02002760static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002761{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002762 struct server *s = check->server;
2763 struct proxy *px = s->proxy;
2764 struct listener *listener = NULL, *l;
2765 int i;
2766 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2767 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002768
Christopher Faulet61cc8522020-04-20 14:54:42 +02002769 list_for_each_entry(l, &px->conf.listeners, by_fe)
2770 /* Use the first INET, INET6 or UNIX listener */
2771 if (l->addr.ss_family == AF_INET ||
2772 l->addr.ss_family == AF_INET6 ||
2773 l->addr.ss_family == AF_UNIX) {
2774 listener = l;
2775 break;
2776 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002777
Christopher Faulet61cc8522020-04-20 14:54:42 +02002778 check->curpid = NULL;
2779 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2780 if (!check->envp) {
2781 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2782 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002783 }
2784
Christopher Faulet61cc8522020-04-20 14:54:42 +02002785 check->argv = calloc(6, sizeof(char *));
2786 if (!check->argv) {
2787 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2788 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002789 }
2790
Christopher Faulet61cc8522020-04-20 14:54:42 +02002791 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002792
Christopher Faulet61cc8522020-04-20 14:54:42 +02002793 if (!listener) {
2794 check->argv[1] = strdup("NOT_USED");
2795 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002796 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002797 else if (listener->addr.ss_family == AF_INET ||
2798 listener->addr.ss_family == AF_INET6) {
2799 addr_to_str(&listener->addr, buf, sizeof(buf));
2800 check->argv[1] = strdup(buf);
2801 port_to_str(&listener->addr, buf, sizeof(buf));
2802 check->argv[2] = strdup(buf);
2803 }
2804 else if (listener->addr.ss_family == AF_UNIX) {
2805 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002806
Christopher Faulet61cc8522020-04-20 14:54:42 +02002807 un = (struct sockaddr_un *)&listener->addr;
2808 check->argv[1] = strdup(un->sun_path);
2809 check->argv[2] = strdup("NOT_USED");
2810 }
2811 else {
2812 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2813 goto err;
2814 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002815
Christopher Faulet61cc8522020-04-20 14:54:42 +02002816 if (!check->argv[1] || !check->argv[2]) {
2817 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2818 goto err;
2819 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002820
Christopher Faulet61cc8522020-04-20 14:54:42 +02002821 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2822 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2823 if (!check->argv[3] || !check->argv[4]) {
2824 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2825 goto err;
2826 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002827
Christopher Faulet61cc8522020-04-20 14:54:42 +02002828 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2829 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2830 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002831
Christopher Faulet61cc8522020-04-20 14:54:42 +02002832 for (i = 0; i < 5; i++) {
2833 if (!check->argv[i]) {
2834 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2835 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002836 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002837 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002838
Christopher Faulet61cc8522020-04-20 14:54:42 +02002839 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2840 /* Add proxy environment variables */
2841 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2842 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2843 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2844 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2845 /* Add server environment variables */
2846 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2847 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2848 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2849 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2850 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2851 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002852
Christopher Faulet61cc8522020-04-20 14:54:42 +02002853 /* Ensure that we don't leave any hole in check->envp */
2854 for (i = 0; i < EXTCHK_SIZE; i++)
2855 if (!check->envp[i])
2856 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002857
Christopher Faulet61cc8522020-04-20 14:54:42 +02002858 return 1;
2859err:
2860 if (check->envp) {
2861 for (i = 0; i < EXTCHK_SIZE; i++)
2862 free(check->envp[i]);
2863 free(check->envp);
2864 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002865 }
2866
Christopher Faulet61cc8522020-04-20 14:54:42 +02002867 if (check->argv) {
2868 for (i = 1; i < 5; i++)
2869 free(check->argv[i]);
2870 free(check->argv);
2871 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002872 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002873 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002874}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002875
Christopher Faulet61cc8522020-04-20 14:54:42 +02002876/*
2877 * establish a server health-check that makes use of a process.
2878 *
2879 * It can return one of :
2880 * - SF_ERR_NONE if everything's OK
2881 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2882 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2883 *
2884 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002885 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002886static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002887{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002888 char buf[256];
2889 struct check *check = t->context;
2890 struct server *s = check->server;
2891 struct proxy *px = s->proxy;
2892 int status;
2893 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002894
Christopher Faulet61cc8522020-04-20 14:54:42 +02002895 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002896
Christopher Faulet61cc8522020-04-20 14:54:42 +02002897 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002898
Christopher Faulet61cc8522020-04-20 14:54:42 +02002899 pid = fork();
2900 if (pid < 0) {
2901 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2902 (global.tune.options & GTUNE_INSECURE_FORK) ?
2903 "" : " (likely caused by missing 'insecure-fork-wanted')",
2904 strerror(errno));
2905 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002906 goto out;
2907 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002908 if (pid == 0) {
2909 /* Child */
2910 extern char **environ;
2911 struct rlimit limit;
2912 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002913
Christopher Faulet61cc8522020-04-20 14:54:42 +02002914 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2915 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002916
Christopher Faulet61cc8522020-04-20 14:54:42 +02002917 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002918
Christopher Faulet61cc8522020-04-20 14:54:42 +02002919 /* restore the initial FD limits */
2920 limit.rlim_cur = rlim_fd_cur_at_boot;
2921 limit.rlim_max = rlim_fd_max_at_boot;
2922 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2923 getrlimit(RLIMIT_NOFILE, &limit);
2924 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2925 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2926 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2927 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002928
Christopher Faulet61cc8522020-04-20 14:54:42 +02002929 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002930
Christopher Faulet61cc8522020-04-20 14:54:42 +02002931 /* Update some environment variables and command args: curconn, server addr and server port */
2932 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002933
Christopher Faulet61cc8522020-04-20 14:54:42 +02002934 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2935 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002936
Christopher Faulet61cc8522020-04-20 14:54:42 +02002937 *check->argv[4] = 0;
2938 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2939 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2940 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002941
Christopher Faulet61cc8522020-04-20 14:54:42 +02002942 haproxy_unblock_signals();
2943 execvp(px->check_command, check->argv);
2944 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2945 strerror(errno));
2946 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002947 }
2948
Christopher Faulet61cc8522020-04-20 14:54:42 +02002949 /* Parent */
2950 if (check->result == CHK_RES_UNKNOWN) {
2951 if (pid_list_add(pid, t) != NULL) {
2952 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2953
2954 if (px->timeout.check && px->timeout.connect) {
2955 int t_con = tick_add(now_ms, px->timeout.connect);
2956 t->expire = tick_first(t->expire, t_con);
2957 }
2958 status = SF_ERR_NONE;
2959 goto out;
2960 }
2961 else {
2962 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2963 }
2964 kill(pid, SIGTERM); /* process creation error */
2965 }
2966 else
2967 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2968
2969out:
2970 unblock_sigchld();
2971 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002972}
2973
Christopher Faulet61cc8522020-04-20 14:54:42 +02002974/*
2975 * manages a server health-check that uses an external process. Returns
2976 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002977 *
2978 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002979 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002980 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002981static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002982{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002983 struct check *check = context;
2984 struct server *s = check->server;
2985 int rv;
2986 int ret;
2987 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002988
Christopher Faulet61cc8522020-04-20 14:54:42 +02002989 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2990 if (!(check->state & CHK_ST_INPROGRESS)) {
2991 /* no check currently running */
2992 if (!expired) /* woke up too early */
2993 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002994
Christopher Faulet61cc8522020-04-20 14:54:42 +02002995 /* we don't send any health-checks when the proxy is
2996 * stopped, the server should not be checked or the check
2997 * is disabled.
2998 */
2999 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3000 s->proxy->state == PR_STSTOPPED)
3001 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003002
Christopher Faulet61cc8522020-04-20 14:54:42 +02003003 /* we'll initiate a new check */
3004 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003005
Christopher Faulet61cc8522020-04-20 14:54:42 +02003006 check->state |= CHK_ST_INPROGRESS;
3007
3008 ret = connect_proc_chk(t);
3009 if (ret == SF_ERR_NONE) {
3010 /* the process was forked, we allow up to min(inter,
3011 * timeout.connect) for it to report its status, but
3012 * only when timeout.check is set as it may be to short
3013 * for a full check otherwise.
3014 */
3015 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3016
3017 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3018 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3019 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003020 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003021 task_set_affinity(t, tid_bit);
3022 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003023 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003024
Christopher Faulet61cc8522020-04-20 14:54:42 +02003025 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003026
Christopher Faulet61cc8522020-04-20 14:54:42 +02003027 check->state &= ~CHK_ST_INPROGRESS;
3028 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003029
Christopher Faulet61cc8522020-04-20 14:54:42 +02003030 /* we allow up to min(inter, timeout.connect) for a connection
3031 * to establish but only when timeout.check is set
3032 * as it may be to short for a full check otherwise
3033 */
3034 while (tick_is_expired(t->expire, now_ms)) {
3035 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003036
Christopher Faulet61cc8522020-04-20 14:54:42 +02003037 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3038 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003039
Christopher Faulet61cc8522020-04-20 14:54:42 +02003040 if (s->proxy->timeout.check)
3041 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003042 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003043 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003044 else {
3045 /* there was a test running.
3046 * First, let's check whether there was an uncaught error,
3047 * which can happen on connect timeout or error.
3048 */
3049 if (check->result == CHK_RES_UNKNOWN) {
3050 /* good connection is enough for pure TCP check */
3051 struct pid_list *elem = check->curpid;
3052 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003053
Christopher Faulet61cc8522020-04-20 14:54:42 +02003054 if (elem->exited) {
3055 status = elem->status; /* Save in case the process exits between use below */
3056 if (!WIFEXITED(status))
3057 check->code = -1;
3058 else
3059 check->code = WEXITSTATUS(status);
3060 if (!WIFEXITED(status) || WEXITSTATUS(status))
3061 status = HCHK_STATUS_PROCERR;
3062 else
3063 status = HCHK_STATUS_PROCOK;
3064 } else if (expired) {
3065 status = HCHK_STATUS_PROCTOUT;
3066 ha_warning("kill %d\n", (int)elem->pid);
3067 kill(elem->pid, SIGTERM);
3068 }
3069 set_server_check_status(check, status, NULL);
3070 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003071
Christopher Faulet61cc8522020-04-20 14:54:42 +02003072 if (check->result == CHK_RES_FAILED) {
3073 /* a failure or timeout detected */
3074 check_notify_failure(check);
3075 }
3076 else if (check->result == CHK_RES_CONDPASS) {
3077 /* check is OK but asks for stopping mode */
3078 check_notify_stopping(check);
3079 }
3080 else if (check->result == CHK_RES_PASSED) {
3081 /* a success was detected */
3082 check_notify_success(check);
3083 }
3084 task_set_affinity(t, 1);
3085 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003086
Christopher Faulet61cc8522020-04-20 14:54:42 +02003087 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003088
Christopher Faulet61cc8522020-04-20 14:54:42 +02003089 rv = 0;
3090 if (global.spread_checks > 0) {
3091 rv = srv_getinter(check) * global.spread_checks / 100;
3092 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3093 }
3094 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3095 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003096
Christopher Faulet61cc8522020-04-20 14:54:42 +02003097 reschedule:
3098 while (tick_is_expired(t->expire, now_ms))
3099 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003100
Christopher Faulet61cc8522020-04-20 14:54:42 +02003101 out_unlock:
3102 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3103 return t;
3104}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003105
Baptiste Assmann248f1172018-03-01 21:49:01 +01003106
Christopher Faulet61cc8522020-04-20 14:54:42 +02003107/**************************************************************************/
3108/***************** Health-checks based on connections *********************/
3109/**************************************************************************/
3110/* This function is used only for server health-checks. It handles connection
3111 * status updates including errors. If necessary, it wakes the check task up.
3112 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3113 * connection (eg: reconnect). It relies on tcpcheck_main().
3114 */
3115static int wake_srv_chk(struct conn_stream *cs)
3116{
3117 struct connection *conn = cs->conn;
3118 struct check *check = cs->data;
3119 struct email_alertq *q = container_of(check, typeof(*q), check);
3120 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003121
Christopher Faulet61cc8522020-04-20 14:54:42 +02003122 if (check->server)
3123 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3124 else
3125 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003126
Christopher Faulet61cc8522020-04-20 14:54:42 +02003127 /* we may have to make progress on the TCP checks */
3128 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003129
Christopher Faulet61cc8522020-04-20 14:54:42 +02003130 cs = check->cs;
3131 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003132
Christopher Faulet61cc8522020-04-20 14:54:42 +02003133 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3134 /* We may get error reports bypassing the I/O handlers, typically
3135 * the case when sending a pure TCP check which fails, then the I/O
3136 * handlers above are not called. This is completely handled by the
3137 * main processing task so let's simply wake it up. If we get here,
3138 * we expect errno to still be valid.
3139 */
3140 chk_report_conn_err(check, errno, 0);
3141 task_wakeup(check->task, TASK_WOKEN_IO);
3142 }
3143
3144 if (check->result != CHK_RES_UNKNOWN) {
3145 /* Check complete or aborted. If connection not yet closed do it
3146 * now and wake the check task up to be sure the result is
3147 * handled ASAP. */
3148 conn_sock_drain(conn);
3149 cs_close(cs);
3150 ret = -1;
3151 /* We may have been scheduled to run, and the
3152 * I/O handler expects to have a cs, so remove
3153 * the tasklet
3154 */
3155 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3156 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003157 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003158
3159 if (check->server)
3160 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003161 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003162 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003163
Christopher Faulet61cc8522020-04-20 14:54:42 +02003164 /* if a connection got replaced, we must absolutely prevent the connection
3165 * handler from touching its fd, and perform the FD polling updates ourselves
3166 */
3167 if (ret < 0)
3168 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003169
Christopher Faulet61cc8522020-04-20 14:54:42 +02003170 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003171}
3172
Christopher Faulet61cc8522020-04-20 14:54:42 +02003173/* This function checks if any I/O is wanted, and if so, attempts to do so */
3174static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003175{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003176 struct check *check = ctx;
3177 struct conn_stream *cs = check->cs;
3178 struct email_alertq *q = container_of(check, typeof(*q), check);
3179 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003180
Christopher Faulet61cc8522020-04-20 14:54:42 +02003181 if (!(check->wait_list.events & SUB_RETRY_SEND))
3182 ret = wake_srv_chk(cs);
3183 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3184 if (check->server)
3185 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3186 else
3187 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003188
Christopher Faulet61cc8522020-04-20 14:54:42 +02003189 if (unlikely(check->result == CHK_RES_FAILED)) {
3190 /* collect possible new errors */
3191 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3192 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003193
Christopher Faulet61cc8522020-04-20 14:54:42 +02003194 /* Reset the check buffer... */
3195 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003196
Christopher Faulet61cc8522020-04-20 14:54:42 +02003197 /* Close the connection... We still attempt to nicely close if,
3198 * for instance, SSL needs to send a "close notify." Later, we perform
3199 * a hard close and reset the connection if some data are pending,
3200 * otherwise we end up with many TIME_WAITs and eat all the source port
3201 * range quickly. To avoid sending RSTs all the time, we first try to
3202 * drain pending data.
3203 */
3204 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3205 * connection, to make sure cs_shutw() will not lead to a shutdown()
3206 * that would provoke TIME_WAITs.
3207 */
3208 cs_shutr(cs, CS_SHR_DRAIN);
3209 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003210
Christopher Faulet61cc8522020-04-20 14:54:42 +02003211 /* OK, let's not stay here forever */
3212 if (check->result == CHK_RES_FAILED)
3213 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003214
Christopher Faulet61cc8522020-04-20 14:54:42 +02003215 task_wakeup(t, TASK_WOKEN_IO);
3216 }
3217
3218 if (check->server)
3219 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3220 else
3221 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003222 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003223 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003224}
3225
Christopher Faulet61cc8522020-04-20 14:54:42 +02003226/* manages a server health-check that uses a connection. Returns
3227 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3228 *
3229 * Please do NOT place any return statement in this function and only leave
3230 * via the out_unlock label.
3231 */
3232static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003233{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003234 struct check *check = context;
3235 struct proxy *proxy = check->proxy;
3236 struct conn_stream *cs = check->cs;
3237 struct connection *conn = cs_conn(cs);
3238 int rv;
3239 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003240
Christopher Faulet61cc8522020-04-20 14:54:42 +02003241 if (check->server)
3242 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3243 if (!(check->state & CHK_ST_INPROGRESS)) {
3244 /* no check currently running */
3245 if (!expired) /* woke up too early */
3246 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003247
Christopher Faulet61cc8522020-04-20 14:54:42 +02003248 /* we don't send any health-checks when the proxy is
3249 * stopped, the server should not be checked or the check
3250 * is disabled.
3251 */
3252 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3253 proxy->state == PR_STSTOPPED)
3254 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003255
Christopher Faulet61cc8522020-04-20 14:54:42 +02003256 /* we'll initiate a new check */
3257 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003258
Christopher Faulet61cc8522020-04-20 14:54:42 +02003259 check->state |= CHK_ST_INPROGRESS;
3260 b_reset(&check->bi);
3261 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003262
Christopher Faulet61cc8522020-04-20 14:54:42 +02003263 task_set_affinity(t, tid_bit);
3264 cs = check->cs;
3265 conn = cs_conn(cs);
3266 if (!conn) {
3267 check->current_step = NULL;
3268 tcpcheck_main(check);
3269 goto out_unlock;
3270 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003271
Christopher Faulet61cc8522020-04-20 14:54:42 +02003272 conn->flags |= CO_FL_ERROR;
3273 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003274
Christopher Faulet61cc8522020-04-20 14:54:42 +02003275 /* here, we have seen a synchronous error, no fd was allocated */
3276 task_set_affinity(t, MAX_THREADS_MASK);
3277 if (cs) {
3278 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003279 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003280 /* We may have been scheduled to run, and the
3281 * I/O handler expects to have a cs, so remove
3282 * the tasklet
3283 */
3284 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3285 cs_destroy(cs);
3286 cs = check->cs = NULL;
3287 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003288 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003289
Christopher Faulet61cc8522020-04-20 14:54:42 +02003290 check->state &= ~CHK_ST_INPROGRESS;
3291 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003292
Christopher Faulet61cc8522020-04-20 14:54:42 +02003293 /* we allow up to min(inter, timeout.connect) for a connection
3294 * to establish but only when timeout.check is set
3295 * as it may be to short for a full check otherwise
3296 */
3297 while (tick_is_expired(t->expire, now_ms)) {
3298 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003299
Christopher Faulet61cc8522020-04-20 14:54:42 +02003300 t_con = tick_add(t->expire, proxy->timeout.connect);
3301 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3302 if (proxy->timeout.check)
3303 t->expire = tick_first(t->expire, t_con);
3304 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003305 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003306 else {
3307 /* there was a test running.
3308 * First, let's check whether there was an uncaught error,
3309 * which can happen on connect timeout or error.
3310 */
3311 if (check->result == CHK_RES_UNKNOWN) {
3312 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3313 chk_report_conn_err(check, 0, expired);
3314 }
3315 else
3316 goto out_unlock; /* timeout not reached, wait again */
3317 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003318
Christopher Faulet61cc8522020-04-20 14:54:42 +02003319 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003320
Christopher Faulet61cc8522020-04-20 14:54:42 +02003321 check->current_step = NULL;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003322
Christopher Faulet61cc8522020-04-20 14:54:42 +02003323 if (conn && conn->xprt) {
3324 /* The check was aborted and the connection was not yet closed.
3325 * This can happen upon timeout, or when an external event such
3326 * as a failed response coupled with "observe layer7" caused the
3327 * server state to be suddenly changed.
3328 */
3329 conn_sock_drain(conn);
3330 cs_close(cs);
3331 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003332
Christopher Faulet61cc8522020-04-20 14:54:42 +02003333 if (cs) {
3334 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003335 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003336 /* We may have been scheduled to run, and the
3337 * I/O handler expects to have a cs, so remove
3338 * the tasklet
3339 */
3340 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3341 cs_destroy(cs);
3342 cs = check->cs = NULL;
3343 conn = NULL;
3344 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003345
Christopher Fauletb693a0d2020-04-27 15:59:22 +02003346 if (check->sess != NULL) {
3347 vars_prune(&check->vars, check->sess, NULL);
3348 session_free(check->sess);
3349 check->sess = NULL;
3350 }
3351
Christopher Faulet61cc8522020-04-20 14:54:42 +02003352 if (check->server) {
3353 if (check->result == CHK_RES_FAILED) {
3354 /* a failure or timeout detected */
3355 check_notify_failure(check);
3356 }
3357 else if (check->result == CHK_RES_CONDPASS) {
3358 /* check is OK but asks for stopping mode */
3359 check_notify_stopping(check);
3360 }
3361 else if (check->result == CHK_RES_PASSED) {
3362 /* a success was detected */
3363 check_notify_success(check);
3364 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003365 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003366 task_set_affinity(t, MAX_THREADS_MASK);
3367 check->state &= ~CHK_ST_INPROGRESS;
3368
3369 if (check->server) {
3370 rv = 0;
3371 if (global.spread_checks > 0) {
3372 rv = srv_getinter(check) * global.spread_checks / 100;
3373 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3374 }
3375 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003376 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003377 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003378
Christopher Faulet61cc8522020-04-20 14:54:42 +02003379 reschedule:
3380 while (tick_is_expired(t->expire, now_ms))
3381 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3382 out_unlock:
3383 if (check->server)
3384 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3385 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003386}
3387
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003388
Christopher Faulet61cc8522020-04-20 14:54:42 +02003389/**************************************************************************/
3390/******************* Internals to parse tcp-check rules *******************/
3391/**************************************************************************/
3392struct action_kw_list tcp_check_keywords = {
3393 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3394};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003395
Christopher Faulet61cc8522020-04-20 14:54:42 +02003396/* Return the struct action_kw associated to a keyword */
3397static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003398{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003399 return action_lookup(&tcp_check_keywords.list, kw);
3400}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003401
Christopher Faulet61cc8522020-04-20 14:54:42 +02003402static void action_kw_tcp_check_build_list(struct buffer *chk)
3403{
3404 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003405}
3406
Christopher Faulet61cc8522020-04-20 14:54:42 +02003407/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3408 * returned on error.
3409 */
3410static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3411 struct list *rules, struct action_kw *kw,
3412 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003413{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003414 struct tcpcheck_rule *chk = NULL;
3415 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003416
Christopher Faulet61cc8522020-04-20 14:54:42 +02003417 actrule = calloc(1, sizeof(*actrule));
3418 if (!actrule) {
3419 memprintf(errmsg, "out of memory");
3420 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003421 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003422 actrule->kw = kw;
3423 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003424
Christopher Faulet61cc8522020-04-20 14:54:42 +02003425 cur_arg++;
3426 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3427 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3428 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003429 }
3430
Christopher Faulet61cc8522020-04-20 14:54:42 +02003431 chk = calloc(1, sizeof(*chk));
3432 if (!chk) {
3433 memprintf(errmsg, "out of memory");
3434 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003435 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003436 chk->action = TCPCHK_ACT_ACTION_KW;
3437 chk->action_kw.rule = actrule;
3438 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003439
3440 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003441 free(actrule);
3442 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003443}
3444
Christopher Faulet61cc8522020-04-20 14:54:42 +02003445/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3446 * returned on error.
3447 */
3448static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3449 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003450{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003451 struct tcpcheck_rule *chk = NULL;
3452 struct sockaddr_storage *sk = NULL;
3453 char *comment = NULL, *sni = NULL, *alpn = NULL;
3454 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003455 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003456 unsigned short conn_opts = 0;
3457 long port = 0;
3458 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003459
Christopher Faulet61cc8522020-04-20 14:54:42 +02003460 list_for_each_entry(chk, rules, list) {
3461 if (chk->action == TCPCHK_ACT_CONNECT)
3462 break;
3463 if (chk->action == TCPCHK_ACT_COMMENT ||
3464 chk->action == TCPCHK_ACT_ACTION_KW ||
3465 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3466 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003467
Christopher Faulet61cc8522020-04-20 14:54:42 +02003468 memprintf(errmsg, "first step MUST also be a 'connect', "
3469 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3470 "when there is a 'connect' step in the tcp-check ruleset");
3471 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003472 }
3473
Christopher Faulet61cc8522020-04-20 14:54:42 +02003474 cur_arg++;
3475 while (*(args[cur_arg])) {
3476 if (strcmp(args[cur_arg], "default") == 0)
3477 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3478 else if (strcmp(args[cur_arg], "addr") == 0) {
3479 int port1, port2;
3480 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003481
Christopher Faulet61cc8522020-04-20 14:54:42 +02003482 if (!*(args[cur_arg+1])) {
3483 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3484 goto error;
3485 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003486
Christopher Faulet61cc8522020-04-20 14:54:42 +02003487 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3488 if (!sk) {
3489 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3490 goto error;
3491 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003492
Christopher Faulet61cc8522020-04-20 14:54:42 +02003493 proto = protocol_by_family(sk->ss_family);
3494 if (!proto || !proto->connect) {
3495 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3496 args[cur_arg]);
3497 goto error;
3498 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003499
Christopher Faulet61cc8522020-04-20 14:54:42 +02003500 if (port1 != port2) {
3501 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3502 args[cur_arg], args[cur_arg+1]);
3503 goto error;
3504 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003505
Christopher Faulet61cc8522020-04-20 14:54:42 +02003506 cur_arg++;
3507 }
3508 else if (strcmp(args[cur_arg], "port") == 0) {
3509 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003510
Christopher Faulet61cc8522020-04-20 14:54:42 +02003511 if (!*(args[cur_arg+1])) {
3512 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3513 goto error;
3514 }
3515 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003516
Christopher Faulet61cc8522020-04-20 14:54:42 +02003517 port = 0;
3518 release_sample_expr(port_expr);
3519 p = args[cur_arg]; end = p + strlen(p);
3520 port = read_uint(&p, end);
3521 if (p != end) {
3522 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003523
Christopher Faulet61cc8522020-04-20 14:54:42 +02003524 px->conf.args.ctx = ARGC_SRV;
3525 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3526 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003527
Christopher Faulet61cc8522020-04-20 14:54:42 +02003528 if (!port_expr) {
3529 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3530 goto error;
3531 }
3532 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3533 memprintf(errmsg, "error detected while parsing port expression : "
3534 " fetch method '%s' extracts information from '%s', "
3535 "none of which is available here.\n",
3536 args[cur_arg], sample_src_names(port_expr->fetch->use));
3537 goto error;
3538 }
3539 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3540 }
3541 else if (port > 65535 || port < 1) {
3542 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3543 args[cur_arg]);
3544 goto error;
3545 }
3546 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003547 else if (strcmp(args[cur_arg], "proto") == 0) {
3548 if (!*(args[cur_arg+1])) {
3549 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3550 goto error;
3551 }
3552 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3553 if (!mux_proto) {
3554 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3555 goto error;
3556 }
3557 cur_arg++;
3558 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003559 else if (strcmp(args[cur_arg], "comment") == 0) {
3560 if (!*(args[cur_arg+1])) {
3561 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3562 goto error;
3563 }
3564 cur_arg++;
3565 free(comment);
3566 comment = strdup(args[cur_arg]);
3567 if (!comment) {
3568 memprintf(errmsg, "out of memory");
3569 goto error;
3570 }
3571 }
3572 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3573 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3574 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3575 conn_opts |= TCPCHK_OPT_SOCKS4;
3576 else if (strcmp(args[cur_arg], "linger") == 0)
3577 conn_opts |= TCPCHK_OPT_LINGER;
3578#ifdef USE_OPENSSL
3579 else if (strcmp(args[cur_arg], "ssl") == 0) {
3580 px->options |= PR_O_TCPCHK_SSL;
3581 conn_opts |= TCPCHK_OPT_SSL;
3582 }
3583 else if (strcmp(args[cur_arg], "sni") == 0) {
3584 if (!*(args[cur_arg+1])) {
3585 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3586 goto error;
3587 }
3588 cur_arg++;
3589 free(sni);
3590 sni = strdup(args[cur_arg]);
3591 if (!sni) {
3592 memprintf(errmsg, "out of memory");
3593 goto error;
3594 }
3595 }
3596 else if (strcmp(args[cur_arg], "alpn") == 0) {
3597#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3598 free(alpn);
3599 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3600 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3601 goto error;
3602 }
3603 cur_arg++;
3604#else
3605 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003606 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003607#endif
3608 }
3609#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003610
Christopher Faulet61cc8522020-04-20 14:54:42 +02003611 else {
3612 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3613#ifdef USE_OPENSSL
3614 ", 'ssl', 'sni', 'alpn'"
3615#endif /* USE_OPENSSL */
3616 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3617 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003618 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003619 }
3620 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621 }
3622
Christopher Faulet61cc8522020-04-20 14:54:42 +02003623 chk = calloc(1, sizeof(*chk));
3624 if (!chk) {
3625 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003626 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003627 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003628 chk->action = TCPCHK_ACT_CONNECT;
3629 chk->comment = comment;
3630 chk->connect.port = port;
3631 chk->connect.options = conn_opts;
3632 chk->connect.sni = sni;
3633 chk->connect.alpn = alpn;
3634 chk->connect.alpn_len= alpn_len;
3635 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003636 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003637 if (sk)
3638 chk->connect.addr = *sk;
3639 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003640
Christopher Faulet61cc8522020-04-20 14:54:42 +02003641 error:
3642 free(alpn);
3643 free(sni);
3644 free(comment);
3645 release_sample_expr(port_expr);
3646 return NULL;
3647}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003648
Christopher Faulet61cc8522020-04-20 14:54:42 +02003649/* Parses and creates a tcp-check send rule. NULL is returned on error */
3650static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3651 const char *file, int line, char **errmsg)
3652{
3653 struct tcpcheck_rule *chk = NULL;
3654 char *comment = NULL, *data = NULL;
3655 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656
Christopher Faulet61cc8522020-04-20 14:54:42 +02003657 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3658 if (!*(args[cur_arg+1])) {
3659 memprintf(errmsg, "'%s' expects a %s as argument",
3660 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003661 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003662 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003663
Christopher Faulet61cc8522020-04-20 14:54:42 +02003664 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003665
Christopher Faulet61cc8522020-04-20 14:54:42 +02003666 cur_arg += 2;
3667 while (*(args[cur_arg])) {
3668 if (strcmp(args[cur_arg], "comment") == 0) {
3669 if (!*(args[cur_arg+1])) {
3670 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3671 goto error;
3672 }
3673 cur_arg++;
3674 free(comment);
3675 comment = strdup(args[cur_arg]);
3676 if (!comment) {
3677 memprintf(errmsg, "out of memory");
3678 goto error;
3679 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003680 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003681 else if (strcmp(args[cur_arg], "log-format") == 0) {
3682 if (type == TCPCHK_SEND_BINARY)
3683 type = TCPCHK_SEND_BINARY_LF;
3684 else if (type == TCPCHK_SEND_STRING)
3685 type = TCPCHK_SEND_STRING_LF;
3686 }
3687 else {
3688 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3689 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003690 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003691 }
3692 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003693 }
3694
Christopher Faulet61cc8522020-04-20 14:54:42 +02003695 chk = calloc(1, sizeof(*chk));
3696 if (!chk) {
3697 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003698 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003699 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003700 chk->action = TCPCHK_ACT_SEND;
3701 chk->comment = comment;
3702 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003703
Christopher Faulet61cc8522020-04-20 14:54:42 +02003704 switch (chk->send.type) {
3705 case TCPCHK_SEND_STRING:
3706 chk->send.data = ist2(strdup(data), strlen(data));
3707 if (!isttest(chk->send.data)) {
3708 memprintf(errmsg, "out of memory");
3709 goto error;
3710 }
3711 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003712 case TCPCHK_SEND_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02003713 int len = chk->send.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003714 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003715 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3716 goto error;
3717 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003718 chk->send.data.len = len;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003719 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02003720 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003721 case TCPCHK_SEND_STRING_LF:
3722 case TCPCHK_SEND_BINARY_LF:
3723 LIST_INIT(&chk->send.fmt);
3724 px->conf.args.ctx = ARGC_SRV;
3725 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3726 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3727 goto error;
3728 }
3729 break;
3730 case TCPCHK_SEND_HTTP:
3731 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003732 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003733 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003734
Christopher Faulet61cc8522020-04-20 14:54:42 +02003735 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003736
Christopher Faulet61cc8522020-04-20 14:54:42 +02003737 error:
3738 free(chk);
3739 free(comment);
3740 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741}
3742
Christopher Faulet61cc8522020-04-20 14:54:42 +02003743/* Parses and creates a http-check send rule. NULL is returned on error */
3744static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3745 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003746{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003747 struct tcpcheck_rule *chk = NULL;
3748 struct tcpcheck_http_hdr *hdr = NULL;
3749 struct http_hdr hdrs[global.tune.max_http_hdr];
3750 char *meth = NULL, *uri = NULL, *vsn = NULL;
3751 char *body = NULL, *comment = NULL;
3752 unsigned int flags = 0;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003753 int i = 0, host_hdr = -1;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003754
3755 cur_arg++;
3756 while (*(args[cur_arg])) {
3757 if (strcmp(args[cur_arg], "meth") == 0) {
3758 if (!*(args[cur_arg+1])) {
3759 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3760 goto error;
3761 }
3762 cur_arg++;
3763 meth = args[cur_arg];
3764 }
3765 else if (strcmp(args[cur_arg], "uri") == 0) {
3766 if (!*(args[cur_arg+1])) {
3767 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3768 goto error;
3769 }
3770 cur_arg++;
3771 uri = args[cur_arg];
3772 // TODO: log-format uri
3773 }
Christopher Faulet907701b2020-04-28 09:37:00 +02003774 else if (strcmp(args[cur_arg], "ver") == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003775 if (!*(args[cur_arg+1])) {
3776 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3777 goto error;
3778 }
3779 cur_arg++;
3780 vsn = args[cur_arg];
3781 }
3782 else if (strcmp(args[cur_arg], "hdr") == 0) {
3783 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3784 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3785 goto error;
3786 }
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003787
3788 if (strcasecmp(args[cur_arg+1], "host") == 0) {
3789 if (host_hdr >= 0) {
3790 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
3791 args[cur_arg+1], istptr(hdrs[host_hdr].v));
3792 goto error;
3793 }
3794 host_hdr = i;
3795 }
Christopher Faulet7d765db2020-04-29 11:50:01 +02003796 else if (strcasecmp(args[cur_arg+1], "connection") == 0 ||
3797 strcasecmp(args[cur_arg+1], "content-length") == 0 ||
3798 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
3799 goto skip_hdr;
Christopher Faulet13ec87b2020-04-29 11:45:44 +02003800
Christopher Faulet61cc8522020-04-20 14:54:42 +02003801 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3802 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3803 i++;
Christopher Faulet7d765db2020-04-29 11:50:01 +02003804 skip_hdr:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003805 cur_arg += 2;
3806 }
3807 else if (strcmp(args[cur_arg], "body") == 0) {
3808 if (!*(args[cur_arg+1])) {
3809 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3810 goto error;
3811 }
3812 cur_arg++;
3813 body = args[cur_arg];
3814 // TODO: log-format body
3815 }
3816 else if (strcmp(args[cur_arg], "comment") == 0) {
3817 if (!*(args[cur_arg+1])) {
3818 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3819 goto error;
3820 }
3821 cur_arg++;
3822 free(comment);
3823 comment = strdup(args[cur_arg]);
3824 if (!comment) {
3825 memprintf(errmsg, "out of memory");
3826 goto error;
3827 }
3828 }
3829 else {
Christopher Faulet907701b2020-04-28 09:37:00 +02003830 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'ver', 'hdr' and 'body' but got '%s' as argument.",
Christopher Faulet61cc8522020-04-20 14:54:42 +02003831 args[cur_arg]);
3832 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003833 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003834 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003835 }
3836
Christopher Faulet61cc8522020-04-20 14:54:42 +02003837 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003838
Christopher Faulet61cc8522020-04-20 14:54:42 +02003839 chk = calloc(1, sizeof(*chk));
3840 if (!chk) {
3841 memprintf(errmsg, "out of memory");
3842 goto error;
3843 }
3844 chk->action = TCPCHK_ACT_SEND;
3845 chk->comment = comment; comment = NULL;
3846 chk->send.type = TCPCHK_SEND_HTTP;
3847 chk->send.http.flags = flags;
3848 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003849
Christopher Faulet61cc8522020-04-20 14:54:42 +02003850 if (meth) {
3851 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3852 chk->send.http.meth.str.area = strdup(meth);
3853 chk->send.http.meth.str.data = strlen(meth);
3854 if (!chk->send.http.meth.str.area) {
3855 memprintf(errmsg, "out of memory");
3856 goto error;
3857 }
3858 }
3859 if (uri) {
3860 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3861 if (!isttest(chk->send.http.uri)) {
3862 memprintf(errmsg, "out of memory");
3863 goto error;
3864 }
3865 }
3866 if (vsn) {
3867 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3868 if (!isttest(chk->send.http.vsn)) {
3869 memprintf(errmsg, "out of memory");
3870 goto error;
3871 }
3872 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003873 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003874 hdr = calloc(1, sizeof(*hdr));
3875 if (!hdr) {
3876 memprintf(errmsg, "out of memory");
3877 goto error;
3878 }
3879 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003880 hdr->name = istdup(hdrs[i].n);
3881 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003882 memprintf(errmsg, "out of memory");
3883 goto error;
3884 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003885
Christopher Fauletb61caf42020-04-21 10:57:42 +02003886 ist0(hdrs[i].v);
3887 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 +02003888 goto error;
3889 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3890 hdr = NULL;
3891 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003892
Christopher Faulet61cc8522020-04-20 14:54:42 +02003893 if (body) {
3894 chk->send.http.body = ist2(strdup(body), strlen(body));
3895 if (!isttest(chk->send.http.body)) {
3896 memprintf(errmsg, "out of memory");
3897 goto error;
3898 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003899 }
3900
Christopher Faulet61cc8522020-04-20 14:54:42 +02003901 return chk;
3902
3903 error:
3904 free_tcpcheck_http_hdr(hdr);
3905 free_tcpcheck(chk, 0);
3906 free(comment);
3907 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003908}
3909
Christopher Faulet61cc8522020-04-20 14:54:42 +02003910/* Parses and creates a http-check comment rule. NULL is returned on error */
3911static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3912 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003913{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914 struct tcpcheck_rule *chk = NULL;
3915 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003916
Christopher Faulet61cc8522020-04-20 14:54:42 +02003917 if (!*(args[cur_arg+1])) {
3918 memprintf(errmsg, "expects a string as argument");
3919 goto error;
3920 }
3921 cur_arg++;
3922 comment = strdup(args[cur_arg]);
3923 if (!comment) {
3924 memprintf(errmsg, "out of memory");
3925 goto error;
3926 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003927
Christopher Faulet61cc8522020-04-20 14:54:42 +02003928 chk = calloc(1, sizeof(*chk));
3929 if (!chk) {
3930 memprintf(errmsg, "out of memory");
3931 goto error;
3932 }
3933 chk->action = TCPCHK_ACT_COMMENT;
3934 chk->comment = comment;
3935 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003936
Christopher Faulet61cc8522020-04-20 14:54:42 +02003937 error:
3938 free(comment);
3939 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003940}
3941
Christopher Faulet61cc8522020-04-20 14:54:42 +02003942/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3943 * on error. <proto> is set to the right protocol flags (covered by the
3944 * TCPCHK_RULES_PROTO_CHK mask).
3945 */
3946static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3947 struct list *rules, unsigned int proto,
3948 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003949{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003950 struct tcpcheck_rule *prev_check, *chk = NULL;
3951 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003952 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003953 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3954 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3955 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3956 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3957 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003958 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003959
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003960 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003961 if (!*(args[cur_arg+1])) {
3962 memprintf(errmsg, "expects at least a matching pattern as arguments");
3963 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003964 }
3965
Christopher Faulet61cc8522020-04-20 14:54:42 +02003966 cur_arg++;
3967 while (*(args[cur_arg])) {
3968 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003969
Christopher Faulet61cc8522020-04-20 14:54:42 +02003970 rescan:
3971 if (strcmp(args[cur_arg], "min-recv") == 0) {
3972 if (in_pattern) {
3973 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3974 goto error;
3975 }
3976 if (!*(args[cur_arg+1])) {
3977 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3978 goto error;
3979 }
3980 /* Use an signed integer here because of chksize */
3981 cur_arg++;
3982 min_recv = atol(args[cur_arg]);
3983 if (min_recv < -1 || min_recv > INT_MAX) {
3984 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3985 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003986 }
3987 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003988 else if (*(args[cur_arg]) == '!') {
3989 in_pattern = 1;
3990 while (*(args[cur_arg]) == '!') {
3991 inverse = !inverse;
3992 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003993 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003994 if (!*(args[cur_arg]))
3995 cur_arg++;
3996 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003998 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3999 if (type != TCPCHK_EXPECT_UNDEF) {
4000 memprintf(errmsg, "only on pattern expected");
4001 goto error;
4002 }
4003 if (proto != TCPCHK_RULES_HTTP_CHK)
4004 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
4005 else
4006 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02004007
Christopher Faulet61cc8522020-04-20 14:54:42 +02004008 if (!*(args[cur_arg+1])) {
4009 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4010 goto error;
4011 }
4012 cur_arg++;
4013 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004014 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004015 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4016 if (proto == TCPCHK_RULES_HTTP_CHK)
4017 goto bad_http_kw;
4018 if (type != TCPCHK_EXPECT_UNDEF) {
4019 memprintf(errmsg, "only on pattern expected");
4020 goto error;
4021 }
4022 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004023
Christopher Faulet61cc8522020-04-20 14:54:42 +02004024 if (!*(args[cur_arg+1])) {
4025 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4026 goto error;
4027 }
4028 cur_arg++;
4029 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004030 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004031 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4032 if (proto != TCPCHK_RULES_HTTP_CHK)
4033 goto bad_tcp_kw;
4034 if (type != TCPCHK_EXPECT_UNDEF) {
4035 memprintf(errmsg, "only on pattern expected");
4036 goto error;
4037 }
4038 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004039
Christopher Faulet61cc8522020-04-20 14:54:42 +02004040 if (!*(args[cur_arg+1])) {
4041 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4042 goto error;
4043 }
4044 cur_arg++;
4045 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004046 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004047 else if (strcmp(args[cur_arg], "custom") == 0) {
4048 if (in_pattern) {
4049 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4050 goto error;
4051 }
4052 if (type != TCPCHK_EXPECT_UNDEF) {
4053 memprintf(errmsg, "only on pattern expected");
4054 goto error;
4055 }
4056 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004057 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004058 else if (strcmp(args[cur_arg], "comment") == 0) {
4059 if (in_pattern) {
4060 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4061 goto error;
4062 }
4063 if (!*(args[cur_arg+1])) {
4064 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4065 goto error;
4066 }
4067 cur_arg++;
4068 free(comment);
4069 comment = strdup(args[cur_arg]);
4070 if (!comment) {
4071 memprintf(errmsg, "out of memory");
4072 goto error;
4073 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004074 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004075 else if (strcmp(args[cur_arg], "on-success") == 0) {
4076 if (in_pattern) {
4077 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4078 goto error;
4079 }
4080 if (!*(args[cur_arg+1])) {
4081 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4082 goto error;
4083 }
4084 cur_arg++;
4085 free(on_success_msg);
4086 on_success_msg = strdup(args[cur_arg]);
4087 if (!on_success_msg) {
4088 memprintf(errmsg, "out of memory");
4089 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004090 }
4091 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004092 else if (strcmp(args[cur_arg], "on-error") == 0) {
4093 if (in_pattern) {
4094 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4095 goto error;
4096 }
4097 if (!*(args[cur_arg+1])) {
4098 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4099 goto error;
4100 }
4101 cur_arg++;
4102 free(on_error_msg);
4103 on_error_msg = strdup(args[cur_arg]);
4104 if (!on_error_msg) {
4105 memprintf(errmsg, "out of memory");
4106 goto error;
4107 }
4108 }
4109 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4110 if (in_pattern) {
4111 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4112 goto error;
4113 }
4114 if (!*(args[cur_arg+1])) {
4115 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4116 goto error;
4117 }
4118 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4119 ok_st = HCHK_STATUS_L7OKD;
4120 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4121 ok_st = HCHK_STATUS_L7OKCD;
4122 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4123 ok_st = HCHK_STATUS_L6OK;
4124 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4125 ok_st = HCHK_STATUS_L4OK;
4126 else {
4127 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4128 args[cur_arg], args[cur_arg+1]);
4129 goto error;
4130 }
4131 cur_arg++;
4132 }
4133 else if (strcmp(args[cur_arg], "error-status") == 0) {
4134 if (in_pattern) {
4135 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4136 goto error;
4137 }
4138 if (!*(args[cur_arg+1])) {
4139 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4140 goto error;
4141 }
4142 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4143 err_st = HCHK_STATUS_L7RSP;
4144 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4145 err_st = HCHK_STATUS_L7STS;
4146 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4147 err_st = HCHK_STATUS_L6RSP;
4148 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4149 err_st = HCHK_STATUS_L4CON;
4150 else {
4151 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4152 args[cur_arg], args[cur_arg+1]);
4153 goto error;
4154 }
4155 cur_arg++;
4156 }
4157 else if (strcmp(args[cur_arg], "status-code") == 0) {
4158 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004159
Christopher Faulet61cc8522020-04-20 14:54:42 +02004160 if (in_pattern) {
4161 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4162 goto error;
4163 }
4164 if (!*(args[cur_arg+1])) {
4165 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4166 goto error;
4167 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004168
Christopher Faulet61cc8522020-04-20 14:54:42 +02004169 cur_arg++;
4170 release_sample_expr(status_expr);
4171 px->conf.args.ctx = ARGC_SRV;
4172 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4173 file, line, errmsg, &px->conf.args, NULL);
4174 if (!status_expr) {
4175 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4176 goto error;
4177 }
4178 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4179 memprintf(errmsg, "error detected while parsing status-code expression : "
4180 " fetch method '%s' extracts information from '%s', "
4181 "none of which is available here.\n",
4182 args[cur_arg], sample_src_names(status_expr->fetch->use));
4183 goto error;
4184 }
4185 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4186 }
4187 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4188 if (in_pattern) {
4189 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4190 goto error;
4191 }
4192 if (!*(args[cur_arg+1])) {
4193 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4194 goto error;
4195 }
4196 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4197 tout_st = HCHK_STATUS_L7TOUT;
4198 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4199 tout_st = HCHK_STATUS_L6TOUT;
4200 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4201 tout_st = HCHK_STATUS_L4TOUT;
4202 else {
4203 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4204 args[cur_arg], args[cur_arg+1]);
4205 goto error;
4206 }
4207 cur_arg++;
4208 }
4209 else {
4210 if (proto == TCPCHK_RULES_HTTP_CHK) {
4211 bad_http_kw:
4212 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4213 " or comment but got '%s' as argument.", args[cur_arg]);
4214 }
4215 else {
4216 bad_tcp_kw:
4217 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4218 " or comment but got '%s' as argument.", args[cur_arg]);
4219 }
4220 goto error;
4221 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004222
Christopher Faulet61cc8522020-04-20 14:54:42 +02004223 cur_arg++;
4224 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004225
Christopher Faulet61cc8522020-04-20 14:54:42 +02004226 chk = calloc(1, sizeof(*chk));
4227 if (!chk) {
4228 memprintf(errmsg, "out of memory");
4229 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004230 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004231 chk->action = TCPCHK_ACT_EXPECT;
4232 LIST_INIT(&chk->expect.onerror_fmt);
4233 LIST_INIT(&chk->expect.onsuccess_fmt);
4234 chk->comment = comment; comment = NULL;
4235 chk->expect.type = type;
4236 chk->expect.min_recv = min_recv;
4237 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004238 chk->expect.ok_status = ok_st;
4239 chk->expect.err_status = err_st;
4240 chk->expect.tout_status = tout_st;
4241 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004242
Christopher Faulet61cc8522020-04-20 14:54:42 +02004243 if (on_success_msg) {
4244 px->conf.args.ctx = ARGC_SRV;
4245 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4246 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4247 goto error;
4248 }
4249 free(on_success_msg);
4250 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004251 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004252 if (on_error_msg) {
4253 px->conf.args.ctx = ARGC_SRV;
4254 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4255 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4256 goto error;
4257 }
4258 free(on_error_msg);
4259 on_error_msg = NULL;
4260 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004261
Christopher Faulet61cc8522020-04-20 14:54:42 +02004262 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004263 case TCPCHK_EXPECT_HTTP_STATUS: {
4264 const char *p = pattern;
4265 unsigned int c1,c2;
4266
4267 chk->expect.codes.codes = NULL;
4268 chk->expect.codes.num = 0;
4269 while (1) {
4270 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4271 if (*p == '-') {
4272 p++;
4273 c2 = read_uint(&p, pattern + strlen(pattern));
4274 }
4275 if (c1 > c2) {
4276 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4277 goto error;
4278 }
4279
4280 chk->expect.codes.num++;
4281 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4282 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4283 if (!chk->expect.codes.codes) {
4284 memprintf(errmsg, "out of memory");
4285 goto error;
4286 }
4287 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4288 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4289
4290 if (*p == '\0')
4291 break;
4292 if (*p != ',') {
4293 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4294 goto error;
4295 }
4296 p++;
4297 }
4298 break;
4299 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004300 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004301 case TCPCHK_EXPECT_HTTP_BODY:
4302 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004303 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004304 memprintf(errmsg, "out of memory");
4305 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004306 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004307 break;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004308 case TCPCHK_EXPECT_BINARY: {
Christopher Faulet9c2cb2d2020-04-28 16:40:41 +02004309 int len = chk->expect.data.len;
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004310
4311 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004312 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4313 goto error;
4314 }
Christopher Faulet2edcd4c2020-04-28 10:39:50 +02004315 chk->expect.data.len = len;
4316 break;
4317 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004318 case TCPCHK_EXPECT_REGEX:
4319 case TCPCHK_EXPECT_REGEX_BINARY:
4320 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4321 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004322 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004323 if (!chk->expect.regex)
4324 goto error;
4325 break;
4326 case TCPCHK_EXPECT_CUSTOM:
4327 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4328 break;
4329 case TCPCHK_EXPECT_UNDEF:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004330 memprintf(errmsg, "pattern not found");
4331 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004332 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004333
Christopher Faulet61cc8522020-04-20 14:54:42 +02004334 /* All tcp-check expect points back to the first inverse expect rule in
4335 * a chain of one or more expect rule, potentially itself.
4336 */
4337 chk->expect.head = chk;
4338 list_for_each_entry_rev(prev_check, rules, list) {
4339 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4340 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4341 chk->expect.head = prev_check;
4342 continue;
4343 }
4344 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4345 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004346 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004347 return chk;
4348
4349 error:
4350 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004351 free(comment);
4352 free(on_success_msg);
4353 free(on_error_msg);
4354 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004355 return NULL;
4356}
4357
Christopher Faulet61cc8522020-04-20 14:54:42 +02004358/* Overwrites fields of the old http send rule with those of the new one. When
4359 * replaced, old values are freed and replaced by the new ones. New values are
4360 * not copied but transferred. At the end <new> should be empty and can be
4361 * safely released. This function never fails.
4362 */
4363static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004364{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004365 struct logformat_node *lf, *lfb;
4366 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004367
Christopher Faulet404f9192020-04-09 23:13:54 +02004368
Christopher Faulet61cc8522020-04-20 14:54:42 +02004369 if (new->send.http.meth.str.area) {
4370 free(old->send.http.meth.str.area);
4371 old->send.http.meth.meth = new->send.http.meth.meth;
4372 old->send.http.meth.str.area = new->send.http.meth.str.area;
4373 old->send.http.meth.str.data = new->send.http.meth.str.data;
4374 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004375 }
4376
Christopher Faulet61cc8522020-04-20 14:54:42 +02004377 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4378 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004379 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004380 else
4381 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4382 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4383 old->send.http.uri = new->send.http.uri;
4384 new->send.http.uri = IST_NULL;
4385 }
4386 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4387 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004388 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004389 else
4390 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4391 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4392 LIST_INIT(&old->send.http.uri_fmt);
4393 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4394 LIST_DEL(&lf->list);
4395 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4396 }
4397 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004398
Christopher Faulet61cc8522020-04-20 14:54:42 +02004399 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004400 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004401 old->send.http.vsn = new->send.http.vsn;
4402 new->send.http.vsn = IST_NULL;
4403 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004404
Christopher Faulet61cc8522020-04-20 14:54:42 +02004405 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4406 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4407 LIST_DEL(&hdr->list);
4408 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004409 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004410
4411 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4412 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004413 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004414 else
4415 free_tcpcheck_fmt(&old->send.http.body_fmt);
4416 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4417 old->send.http.body = new->send.http.body;
4418 new->send.http.body = IST_NULL;
4419 }
4420 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4421 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004422 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004423 else
4424 free_tcpcheck_fmt(&old->send.http.body_fmt);
4425 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4426 LIST_INIT(&old->send.http.body_fmt);
4427 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4428 LIST_DEL(&lf->list);
4429 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4430 }
4431 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004432}
4433
Christopher Faulet61cc8522020-04-20 14:54:42 +02004434/* Internal function used to add an http-check rule in a list during the config
4435 * parsing step. Depending on its type, and the previously inserted rules, a
4436 * specific action may be performed or an error may be reported. This functions
4437 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4438 * message.
4439 */
4440static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004441{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004442 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004443
Christopher Faulet61cc8522020-04-20 14:54:42 +02004444 /* the implicit send rule coming from an "option httpchk" line must be
4445 * merged with the first explici http-check send rule, if
4446 * any. Depdending the declaration order some tests are required.
4447 *
4448 * Some tests is also required for other kinds of http-check rules to be
4449 * sure the ruleset remains valid.
4450 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004451
Christopher Faulet61cc8522020-04-20 14:54:42 +02004452 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4453 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4454 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4455 * following tests are performed :
4456 *
4457 * 1- If there is no such rule or if it is not a send rule, the implicit send
4458 * rule is pushed in front of the ruleset
4459 *
4460 * 2- If it is another implicit send rule, it is replaced with the new one.
4461 *
4462 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4463 * both, overwritting the old send rule (the explicit one) with info of the
4464 * new send rule (the implicit one).
4465 */
4466 r = get_first_tcpcheck_rule(rules);
4467 if (r && r->action == TCPCHK_ACT_CONNECT)
4468 r = get_next_tcpcheck_rule(rules, r);
4469 if (!r || r->action != TCPCHK_ACT_SEND)
4470 LIST_ADD(rules->list, &chk->list);
4471 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4472 LIST_DEL(&r->list);
4473 free_tcpcheck(r, 0);
4474 LIST_ADD(rules->list, &chk->list);
4475 }
4476 else {
4477 tcpcheck_overwrite_send_http_rule(r, chk);
4478 free_tcpcheck(chk, 0);
4479 }
4480 }
4481 else {
4482 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4483 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4484 * with an existing implicit send rule, if any. At the end, if there is no error,
4485 * the rule is appended to the list.
4486 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004487
Christopher Faulet61cc8522020-04-20 14:54:42 +02004488 r = get_last_tcpcheck_rule(rules);
4489 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4490 /* no error */;
4491 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4492 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4493 chk->index+1);
4494 return 0;
4495 }
4496 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4497 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4498 chk->index+1);
4499 return 0;
4500 }
4501 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4502 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4503 chk->index+1);
4504 return 0;
4505 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004506
Christopher Faulet61cc8522020-04-20 14:54:42 +02004507 if (chk->action == TCPCHK_ACT_SEND) {
4508 r = get_first_tcpcheck_rule(rules);
4509 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4510 tcpcheck_overwrite_send_http_rule(r, chk);
4511 free_tcpcheck(chk, 0);
4512 LIST_DEL(&r->list);
4513 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4514 chk = r;
4515 }
4516 }
4517 LIST_ADDQ(rules->list, &chk->list);
4518 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004519 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004520}
4521
Christopher Faulet61cc8522020-04-20 14:54:42 +02004522/**************************************************************************/
4523/************************** Init/deinit checks ****************************/
4524/**************************************************************************/
4525static const char *init_check(struct check *check, int type)
4526{
4527 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004528
Christopher Faulet61cc8522020-04-20 14:54:42 +02004529 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4530 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004531
Christopher Faulet61cc8522020-04-20 14:54:42 +02004532 check->bi.area = calloc(check->bi.size, sizeof(char));
4533 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004534
Christopher Faulet61cc8522020-04-20 14:54:42 +02004535 if (!check->bi.area || !check->bo.area)
4536 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004537
Christopher Faulet61cc8522020-04-20 14:54:42 +02004538 check->wait_list.tasklet = tasklet_new();
4539 if (!check->wait_list.tasklet)
4540 return "out of memory while allocating check tasklet";
4541 check->wait_list.events = 0;
4542 check->wait_list.tasklet->process = event_srv_chk_io;
4543 check->wait_list.tasklet->context = check;
4544 return NULL;
4545}
4546
4547void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004548{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004549 task_destroy(check->task);
4550 if (check->wait_list.tasklet)
4551 tasklet_free(check->wait_list.tasklet);
4552
4553 free(check->bi.area);
4554 free(check->bo.area);
4555 if (check->cs) {
4556 free(check->cs->conn);
4557 check->cs->conn = NULL;
4558 cs_free(check->cs);
4559 check->cs = NULL;
4560 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004561}
4562
Christopher Faulet61cc8522020-04-20 14:54:42 +02004563/* manages a server health-check. Returns the time the task accepts to wait, or
4564 * TIME_ETERNITY for infinity.
4565 */
4566static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004567{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004568 struct check *check = context;
4569
4570 if (check->type == PR_O2_EXT_CHK)
4571 return process_chk_proc(t, context, state);
4572 return process_chk_conn(t, context, state);
4573
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004574}
4575
Christopher Faulet61cc8522020-04-20 14:54:42 +02004576
4577static int start_check_task(struct check *check, int mininter,
4578 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004579{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004580 struct task *t;
4581 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004582
Christopher Faulet61cc8522020-04-20 14:54:42 +02004583 if (check->type == PR_O2_EXT_CHK)
4584 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004585
Christopher Faulet61cc8522020-04-20 14:54:42 +02004586 /* task for the check */
4587 if ((t = task_new(thread_mask)) == NULL) {
4588 ha_alert("Starting [%s:%s] check: out of memory.\n",
4589 check->server->proxy->id, check->server->id);
4590 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004591 }
4592
Christopher Faulet61cc8522020-04-20 14:54:42 +02004593 check->task = t;
4594 t->process = process_chk;
4595 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004596
Christopher Faulet61cc8522020-04-20 14:54:42 +02004597 if (mininter < srv_getinter(check))
4598 mininter = srv_getinter(check);
4599
4600 if (global.max_spread_checks && mininter > global.max_spread_checks)
4601 mininter = global.max_spread_checks;
4602
4603 /* check this every ms */
4604 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4605 check->start = now;
4606 task_queue(t);
4607
4608 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004609}
4610
Christopher Faulet61cc8522020-04-20 14:54:42 +02004611/* updates the server's weight during a warmup stage. Once the final weight is
4612 * reached, the task automatically stops. Note that any server status change
4613 * must have updated s->last_change accordingly.
4614 */
4615static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004616{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004617 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004618
Christopher Faulet61cc8522020-04-20 14:54:42 +02004619 /* by default, plan on stopping the task */
4620 t->expire = TICK_ETERNITY;
4621 if ((s->next_admin & SRV_ADMF_MAINT) ||
4622 (s->next_state != SRV_ST_STARTING))
4623 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004624
Christopher Faulet61cc8522020-04-20 14:54:42 +02004625 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004626
Christopher Faulet61cc8522020-04-20 14:54:42 +02004627 /* recalculate the weights and update the state */
4628 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004629
Christopher Faulet61cc8522020-04-20 14:54:42 +02004630 /* probably that we can refill this server with a bit more connections */
4631 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004632
Christopher Faulet61cc8522020-04-20 14:54:42 +02004633 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004634
Christopher Faulet61cc8522020-04-20 14:54:42 +02004635 /* get back there in 1 second or 1/20th of the slowstart interval,
4636 * whichever is greater, resulting in small 5% steps.
4637 */
4638 if (s->next_state == SRV_ST_STARTING)
4639 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4640 return t;
4641}
4642
4643/*
4644 * Start health-check.
4645 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4646 */
4647static int start_checks()
4648{
4649
4650 struct proxy *px;
4651 struct server *s;
4652 struct task *t;
4653 int nbcheck=0, mininter=0, srvpos=0;
4654
4655 /* 0- init the dummy frontend used to create all checks sessions */
4656 init_new_proxy(&checks_fe);
4657 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4658 checks_fe.mode = PR_MODE_TCP;
4659 checks_fe.maxconn = 0;
4660 checks_fe.conn_retries = CONN_RETRIES;
4661 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4662 checks_fe.timeout.client = TICK_ETERNITY;
4663
4664 /* 1- count the checkers to run simultaneously.
4665 * We also determine the minimum interval among all of those which
4666 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4667 * will be used to spread their start-up date. Those which have
4668 * a shorter interval will start independently and will not dictate
4669 * too short an interval for all others.
4670 */
4671 for (px = proxies_list; px; px = px->next) {
4672 for (s = px->srv; s; s = s->next) {
4673 if (s->slowstart) {
4674 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4675 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4676 return ERR_ALERT | ERR_FATAL;
4677 }
4678 /* We need a warmup task that will be called when the server
4679 * state switches from down to up.
4680 */
4681 s->warmup = t;
4682 t->process = server_warmup;
4683 t->context = s;
4684 /* server can be in this state only because of */
4685 if (s->next_state == SRV_ST_STARTING)
4686 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 +02004687 }
4688
Christopher Faulet61cc8522020-04-20 14:54:42 +02004689 if (s->check.state & CHK_ST_CONFIGURED) {
4690 nbcheck++;
4691 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4692 (!mininter || mininter > srv_getinter(&s->check)))
4693 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004694 }
4695
Christopher Faulet61cc8522020-04-20 14:54:42 +02004696 if (s->agent.state & CHK_ST_CONFIGURED) {
4697 nbcheck++;
4698 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4699 (!mininter || mininter > srv_getinter(&s->agent)))
4700 mininter = srv_getinter(&s->agent);
4701 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004702 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004703 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004704
Christopher Faulet61cc8522020-04-20 14:54:42 +02004705 if (!nbcheck)
4706 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004707
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004709
Christopher Faulet61cc8522020-04-20 14:54:42 +02004710 /*
4711 * 2- start them as far as possible from each others. For this, we will
4712 * start them after their interval set to the min interval divided by
4713 * the number of servers, weighted by the server's position in the list.
4714 */
4715 for (px = proxies_list; px; px = px->next) {
4716 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4717 if (init_pid_list()) {
4718 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4719 return ERR_ALERT | ERR_FATAL;
4720 }
4721 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004722
Christopher Faulet61cc8522020-04-20 14:54:42 +02004723 for (s = px->srv; s; s = s->next) {
4724 /* A task for the main check */
4725 if (s->check.state & CHK_ST_CONFIGURED) {
4726 if (s->check.type == PR_O2_EXT_CHK) {
4727 if (!prepare_external_check(&s->check))
4728 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004729 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004730 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4731 return ERR_ALERT | ERR_FATAL;
4732 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004733 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004734
Christopher Faulet61cc8522020-04-20 14:54:42 +02004735 /* A task for a auxiliary agent check */
4736 if (s->agent.state & CHK_ST_CONFIGURED) {
4737 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4738 return ERR_ALERT | ERR_FATAL;
4739 }
4740 srvpos++;
4741 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004742 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004743 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004744 return 0;
4745}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004746
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004747
Christopher Faulet61cc8522020-04-20 14:54:42 +02004748/*
4749 * Return value:
4750 * the port to be used for the health check
4751 * 0 in case no port could be found for the check
4752 */
4753static int srv_check_healthcheck_port(struct check *chk)
4754{
4755 int i = 0;
4756 struct server *srv = NULL;
4757
4758 srv = chk->server;
4759
4760 /* by default, we use the health check port ocnfigured */
4761 if (chk->port > 0)
4762 return chk->port;
4763
4764 /* try to get the port from check_core.addr if check.port not set */
4765 i = get_host_port(&chk->addr);
4766 if (i > 0)
4767 return i;
4768
4769 /* try to get the port from server address */
4770 /* prevent MAPPORTS from working at this point, since checks could
4771 * not be performed in such case (MAPPORTS impose a relative ports
4772 * based on live traffic)
4773 */
4774 if (srv->flags & SRV_F_MAPPORTS)
4775 return 0;
4776
4777 i = srv->svc_port; /* by default */
4778 if (i > 0)
4779 return i;
4780
4781 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004782}
4783
Christopher Faulet61cc8522020-04-20 14:54:42 +02004784/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4785 * if an error occurred.
4786 */
4787static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004788{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004789 const char *err;
4790 struct tcpcheck_rule *r;
4791 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004792
Christopher Faulet61cc8522020-04-20 14:54:42 +02004793 if (!srv->do_check)
4794 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004795
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004796
Christopher Faulet61cc8522020-04-20 14:54:42 +02004797 /* If neither a port nor an addr was specified and no check transport
4798 * layer is forced, then the transport layer used by the checks is the
4799 * same as for the production traffic. Otherwise we use raw_sock by
4800 * default, unless one is specified.
4801 */
4802 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4803 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4804 srv->check.use_ssl = srv->use_ssl;
4805 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004806 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004807 else if (srv->check.use_ssl == 1)
4808 srv->check.xprt = xprt_get(XPRT_SSL);
4809 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004810 }
4811
Christopher Faulet12882cf2020-04-23 15:50:18 +02004812 /* Inherit the mux protocol from the server if not already defined for
4813 * the check
4814 */
4815 if (srv->mux_proto && !srv->check.mux_proto)
4816 srv->check.mux_proto = srv->mux_proto;
4817
Christopher Faulet61cc8522020-04-20 14:54:42 +02004818 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004819
Christopher Faulet61cc8522020-04-20 14:54:42 +02004820 /* We need at least a service port, a check port or the first tcp-check
4821 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4822 */
4823 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4824 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4825 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004826
Christopher Faulet61cc8522020-04-20 14:54:42 +02004827 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4828 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4829 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4830 ret |= ERR_ALERT | ERR_ABORT;
4831 goto out;
4832 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004833
Christopher Faulet61cc8522020-04-20 14:54:42 +02004834 /* search the first action (connect / send / expect) in the list */
4835 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4836 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4837 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4838 "nor tcp_check rule 'connect' with port information.\n",
4839 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4840 ret |= ERR_ALERT | ERR_ABORT;
4841 goto out;
4842 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004843
Christopher Faulet61cc8522020-04-20 14:54:42 +02004844 /* scan the tcp-check ruleset to ensure a port has been configured */
4845 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4846 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4847 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4848 "and a tcp_check rule 'connect' with no port information.\n",
4849 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4850 ret |= ERR_ALERT | ERR_ABORT;
4851 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004852 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004853 }
4854
Christopher Faulet61cc8522020-04-20 14:54:42 +02004855 init:
4856 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4857 struct tcpcheck_ruleset *rs = NULL;
4858 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4859 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004860
Christopher Faulet61cc8522020-04-20 14:54:42 +02004861 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4862 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004863
Christopher Faulet61cc8522020-04-20 14:54:42 +02004864 rs = find_tcpcheck_ruleset("*tcp-check");
4865 if (!rs) {
4866 rs = create_tcpcheck_ruleset("*tcp-check");
4867 if (rs == NULL) {
4868 ha_alert("config: %s '%s': out of memory.\n",
4869 proxy_type_str(srv->proxy), srv->proxy->id);
4870 ret |= ERR_ALERT | ERR_FATAL;
4871 goto out;
4872 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004873 }
4874
Christopher Faulet61cc8522020-04-20 14:54:42 +02004875 free_tcpcheck_vars(&rules->preset_vars);
4876 rules->list = &rs->rules;
4877 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004878 }
4879
Christopher Faulet61cc8522020-04-20 14:54:42 +02004880 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4881 if (err) {
4882 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4883 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4884 ret |= ERR_ALERT | ERR_ABORT;
4885 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004886 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004887 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4888 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004889
Christopher Faulet61cc8522020-04-20 14:54:42 +02004890 out:
4891 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004892}
4893
Christopher Faulet61cc8522020-04-20 14:54:42 +02004894/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4895 * if an error occurred.
4896 */
4897static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004898{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004899 struct tcpcheck_rule *chk;
4900 const char *err;
4901 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004902
Christopher Faulet61cc8522020-04-20 14:54:42 +02004903 if (!srv->do_agent)
4904 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004905
Christopher Faulet61cc8522020-04-20 14:54:42 +02004906 /* If there is no connect rule preceeding all send / expect rules, an
4907 * implicit one is inserted before all others.
4908 */
4909 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4910 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4911 chk = calloc(1, sizeof(*chk));
4912 if (!chk) {
4913 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4914 " to agent-check for server '%s' (out of memory).\n",
4915 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4916 ret |= ERR_ALERT | ERR_FATAL;
4917 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004918 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004919 chk->action = TCPCHK_ACT_CONNECT;
4920 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4921 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004922 }
4923
Christopher Faulete5870d82020-04-15 11:32:03 +02004924
Christopher Faulet61cc8522020-04-20 14:54:42 +02004925 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4926 if (err) {
4927 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4928 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4929 ret |= ERR_ALERT | ERR_ABORT;
4930 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004931 }
4932
Christopher Faulet61cc8522020-04-20 14:54:42 +02004933 if (!srv->agent.inter)
4934 srv->agent.inter = srv->check.inter;
4935
4936 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4937 global.maxsock++;
4938
4939 out:
4940 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004941}
4942
Christopher Faulet61cc8522020-04-20 14:54:42 +02004943/* Check tcp-check health-check configuration for the proxy <px>. */
4944static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004945{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004946 struct tcpcheck_rule *chk, *back;
4947 char *comment = NULL, *errmsg = NULL;
4948 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4949 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004950
Christopher Faulet61cc8522020-04-20 14:54:42 +02004951 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4952 deinit_proxy_tcpcheck(px);
4953 goto out;
4954 }
4955
4956 free(px->check_command);
4957 free(px->check_path);
4958 px->check_command = px->check_path = NULL;
4959
4960 if (!px->tcpcheck_rules.list) {
4961 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4962 ret |= ERR_ALERT | ERR_FATAL;
4963 goto out;
4964 }
4965
4966 /* HTTP ruleset only : */
4967 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4968 struct tcpcheck_rule *next;
4969
4970 /* move remaining implicit send rule from "option httpchk" line to the right place.
4971 * If such rule exists, it must be the first one. In this case, the rule is moved
4972 * after the first connect rule, if any. Otherwise, nothing is done.
4973 */
4974 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4975 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4976 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4977 if (next && next->action == TCPCHK_ACT_CONNECT) {
4978 LIST_DEL(&chk->list);
4979 LIST_ADD(&next->list, &chk->list);
4980 chk->index = next->index;
4981 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004982 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004983
4984 /* add implicit expect rule if the last one is a send. It is inherited from previous
4985 * versions where the http expect rule was optional. Now it is possible to chained
4986 * send/expect rules but the last expect may still be implicit.
4987 */
4988 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4989 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004990 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004991 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4992 px->conf.file, px->conf.line, &errmsg);
4993 if (!next) {
4994 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4995 "(%s).\n", px->id, errmsg);
4996 free(errmsg);
4997 ret |= ERR_ALERT | ERR_FATAL;
4998 goto out;
4999 }
5000 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
5001 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02005002 }
5003 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005004
5005 /* For all ruleset: */
5006
5007 /* If there is no connect rule preceeding all send / expect rules, an
5008 * implicit one is inserted before all others.
5009 */
5010 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
5011 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
5012 chk = calloc(1, sizeof(*chk));
5013 if (!chk) {
5014 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
5015 "(out of memory).\n", px->id);
5016 ret |= ERR_ALERT | ERR_FATAL;
5017 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02005018 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005019 chk->action = TCPCHK_ACT_CONNECT;
5020 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
5021 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
5022 }
5023
5024 /* Remove all comment rules. To do so, when a such rule is found, the
5025 * comment is assigned to the following rule(s).
5026 */
5027 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5028 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5029 free(comment);
5030 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005031 }
5032
Christopher Faulet61cc8522020-04-20 14:54:42 +02005033 prev_action = chk->action;
5034 switch (chk->action) {
5035 case TCPCHK_ACT_COMMENT:
5036 free(comment);
5037 comment = chk->comment;
5038 LIST_DEL(&chk->list);
5039 free(chk);
5040 break;
5041 case TCPCHK_ACT_CONNECT:
5042 if (!chk->comment && comment)
5043 chk->comment = strdup(comment);
5044 /* fall though */
5045 case TCPCHK_ACT_ACTION_KW:
5046 free(comment);
5047 comment = NULL;
5048 break;
5049 case TCPCHK_ACT_SEND:
5050 case TCPCHK_ACT_EXPECT:
5051 if (!chk->comment && comment)
5052 chk->comment = strdup(comment);
5053 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005054 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005055 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005056 free(comment);
5057 comment = NULL;
5058
5059 out:
5060 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005061}
5062
Christopher Faulet61cc8522020-04-20 14:54:42 +02005063void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005064{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005065 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5066 px->tcpcheck_rules.flags = 0;
5067 px->tcpcheck_rules.list = NULL;
5068}
Christopher Faulete5870d82020-04-15 11:32:03 +02005069
Christopher Faulet61cc8522020-04-20 14:54:42 +02005070static void deinit_srv_check(struct server *srv)
5071{
5072 if (srv->check.state & CHK_ST_CONFIGURED)
5073 free_check(&srv->check);
5074 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5075 srv->do_check = 0;
5076}
Christopher Faulete5870d82020-04-15 11:32:03 +02005077
Christopher Faulet61cc8522020-04-20 14:54:42 +02005078
5079static void deinit_srv_agent_check(struct server *srv)
5080{
5081 if (srv->agent.tcpcheck_rules) {
5082 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5083 free(srv->agent.tcpcheck_rules);
5084 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005085 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005086
Christopher Faulet61cc8522020-04-20 14:54:42 +02005087 if (srv->agent.state & CHK_ST_CONFIGURED)
5088 free_check(&srv->agent);
5089
5090 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5091 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005092}
5093
Christopher Faulet61cc8522020-04-20 14:54:42 +02005094static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005095{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005096 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005097 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005098 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005099
Christopher Fauletd7cee712020-04-21 13:45:00 +02005100 node = ebpt_first(&shared_tcpchecks);
5101 while (node) {
5102 next = ebpt_next(node);
5103 ebpt_delete(node);
5104 free(node->key);
5105 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005106 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5107 LIST_DEL(&r->list);
5108 free_tcpcheck(r, 0);
5109 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005110 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005111 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005112 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005113}
Christopher Faulete5870d82020-04-15 11:32:03 +02005114
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005115
Christopher Faulet61cc8522020-04-20 14:54:42 +02005116REGISTER_POST_SERVER_CHECK(init_srv_check);
5117REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5118REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5119REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005120
Christopher Faulet61cc8522020-04-20 14:54:42 +02005121REGISTER_SERVER_DEINIT(deinit_srv_check);
5122REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5123REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5124REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005125
Christopher Faulet61cc8522020-04-20 14:54:42 +02005126/**************************************************************************/
5127/****************************** Email alerts ******************************/
5128/* NOTE: It may be pertinent to use an applet to handle email alerts */
5129/* instead of a tcp-check ruleset */
5130/**************************************************************************/
5131void email_alert_free(struct email_alert *alert)
5132{
5133 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005134
Christopher Faulet61cc8522020-04-20 14:54:42 +02005135 if (!alert)
5136 return;
5137
5138 if (alert->rules.list) {
5139 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5140 LIST_DEL(&rule->list);
5141 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005142 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005143 free_tcpcheck_vars(&alert->rules.preset_vars);
5144 free(alert->rules.list);
5145 alert->rules.list = NULL;
5146 }
5147 pool_free(pool_head_email_alert, alert);
5148}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005149
Christopher Faulet61cc8522020-04-20 14:54:42 +02005150static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5151{
5152 struct check *check = context;
5153 struct email_alertq *q;
5154 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005155
Christopher Faulet61cc8522020-04-20 14:54:42 +02005156 q = container_of(check, typeof(*q), check);
5157
5158 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5159 while (1) {
5160 if (!(check->state & CHK_ST_ENABLED)) {
5161 if (LIST_ISEMPTY(&q->email_alerts)) {
5162 /* All alerts processed, queue the task */
5163 t->expire = TICK_ETERNITY;
5164 task_queue(t);
5165 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005166 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005167
5168 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5169 LIST_DEL(&alert->list);
5170 t->expire = now_ms;
5171 check->tcpcheck_rules = &alert->rules;
5172 check->status = HCHK_STATUS_INI;
5173 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005174 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005175
5176 process_chk(t, context, state);
5177 if (check->state & CHK_ST_INPROGRESS)
5178 break;
5179
5180 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5181 email_alert_free(alert);
5182 check->tcpcheck_rules = NULL;
5183 check->server = NULL;
5184 check->state &= ~CHK_ST_ENABLED;
5185 }
5186 end:
5187 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5188 return t;
5189}
5190
5191/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5192 *
5193 * The function returns 1 in success case, otherwise, it returns 0 and err is
5194 * filled.
5195 */
5196int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5197{
5198 struct mailer *mailer;
5199 struct email_alertq *queues;
5200 const char *err_str;
5201 int i = 0;
5202
5203 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5204 memprintf(err, "out of memory while allocating mailer alerts queues");
5205 goto fail_no_queue;
5206 }
5207
5208 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5209 struct email_alertq *q = &queues[i];
5210 struct check *check = &q->check;
5211 struct task *t;
5212
5213 LIST_INIT(&q->email_alerts);
5214 HA_SPIN_INIT(&q->lock);
5215 check->inter = mls->timeout.mail;
5216 check->rise = DEF_AGENT_RISETIME;
5217 check->proxy = p;
5218 check->fall = DEF_AGENT_FALLTIME;
5219 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5220 memprintf(err, "%s", err_str);
5221 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005222 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005223
5224 check->xprt = mailer->xprt;
5225 check->addr = mailer->addr;
5226 check->port = get_host_port(&mailer->addr);
5227
5228 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5229 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005230 goto error;
5231 }
5232
Christopher Faulet61cc8522020-04-20 14:54:42 +02005233 check->task = t;
5234 t->process = process_email_alert;
5235 t->context = check;
5236
5237 /* check this in one ms */
5238 t->expire = TICK_ETERNITY;
5239 check->start = now;
5240 task_queue(t);
5241 }
5242
5243 mls->users++;
5244 free(p->email_alert.mailers.name);
5245 p->email_alert.mailers.m = mls;
5246 p->email_alert.queues = queues;
5247 return 0;
5248
5249 error:
5250 for (i = 0; i < mls->count; i++) {
5251 struct email_alertq *q = &queues[i];
5252 struct check *check = &q->check;
5253
5254 free_check(check);
5255 }
5256 free(queues);
5257 fail_no_queue:
5258 return 1;
5259}
5260
5261static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5262{
5263 struct tcpcheck_rule *tcpcheck, *prev_check;
5264 struct tcpcheck_expect *expect;
5265
5266 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5267 return 0;
5268 memset(tcpcheck, 0, sizeof(*tcpcheck));
5269 tcpcheck->action = TCPCHK_ACT_EXPECT;
5270
5271 expect = &tcpcheck->expect;
5272 expect->type = TCPCHK_EXPECT_STRING;
5273 LIST_INIT(&expect->onerror_fmt);
5274 LIST_INIT(&expect->onsuccess_fmt);
5275 expect->ok_status = HCHK_STATUS_L7OKD;
5276 expect->err_status = HCHK_STATUS_L7RSP;
5277 expect->tout_status = HCHK_STATUS_L7TOUT;
5278 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005279 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005280 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5281 return 0;
5282 }
5283
5284 /* All tcp-check expect points back to the first inverse expect rule
5285 * in a chain of one or more expect rule, potentially itself.
5286 */
5287 tcpcheck->expect.head = tcpcheck;
5288 list_for_each_entry_rev(prev_check, rules->list, list) {
5289 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5290 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5291 tcpcheck->expect.head = prev_check;
5292 continue;
5293 }
5294 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5295 break;
5296 }
5297 LIST_ADDQ(rules->list, &tcpcheck->list);
5298 return 1;
5299}
5300
5301static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5302{
5303 struct tcpcheck_rule *tcpcheck;
5304 struct tcpcheck_send *send;
5305 const char *in;
5306 char *dst;
5307 int i;
5308
5309 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5310 return 0;
5311 memset(tcpcheck, 0, sizeof(*tcpcheck));
5312 tcpcheck->action = TCPCHK_ACT_SEND;
5313
5314 send = &tcpcheck->send;
5315 send->type = TCPCHK_SEND_STRING;
5316
5317 for (i = 0; strs[i]; i++)
5318 send->data.len += strlen(strs[i]);
5319
Christopher Fauletb61caf42020-04-21 10:57:42 +02005320 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005321 if (!isttest(send->data)) {
5322 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5323 return 0;
5324 }
5325
Christopher Fauletb61caf42020-04-21 10:57:42 +02005326 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005327 for (i = 0; strs[i]; i++)
5328 for (in = strs[i]; (*dst = *in++); dst++);
5329 *dst = 0;
5330
5331 LIST_ADDQ(rules->list, &tcpcheck->list);
5332 return 1;
5333}
5334
5335static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5336 struct email_alertq *q, const char *msg)
5337{
5338 struct email_alert *alert;
5339 struct tcpcheck_rule *tcpcheck;
5340 struct check *check = &q->check;
5341
5342 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5343 goto error;
5344 LIST_INIT(&alert->list);
5345 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5346 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5347 if (!alert->rules.list)
5348 goto error;
5349 LIST_INIT(alert->rules.list);
5350 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5351 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005352
Christopher Faulet61cc8522020-04-20 14:54:42 +02005353 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5354 goto error;
5355 memset(tcpcheck, 0, sizeof(*tcpcheck));
5356 tcpcheck->action = TCPCHK_ACT_CONNECT;
5357 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005358
Christopher Faulet61cc8522020-04-20 14:54:42 +02005359 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005360
Christopher Faulet61cc8522020-04-20 14:54:42 +02005361 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005362 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005363
5364 {
5365 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5366 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5367 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005368 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005369
Christopher Faulet61cc8522020-04-20 14:54:42 +02005370 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5371 goto error;
5372
5373 {
5374 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5375 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005376 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005377 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005378
5379 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5380 goto error;
5381
5382 {
5383 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5384 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005385 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005386 }
5387
Christopher Faulet61cc8522020-04-20 14:54:42 +02005388 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5389 goto error;
5390
5391 {
5392 const char * const strs[2] = { "DATA\r\n" };
5393 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005394 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005395 }
5396
5397 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5398 goto error;
5399
5400 {
5401 struct tm tm;
5402 char datestr[48];
5403 const char * const strs[18] = {
5404 "From: ", p->email_alert.from, "\r\n",
5405 "To: ", p->email_alert.to, "\r\n",
5406 "Date: ", datestr, "\r\n",
5407 "Subject: [HAproxy Alert] ", msg, "\r\n",
5408 "\r\n",
5409 msg, "\r\n",
5410 "\r\n",
5411 ".\r\n",
5412 NULL
5413 };
5414
5415 get_localtime(date.tv_sec, &tm);
5416
5417 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005418 goto error;
5419 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005420
5421 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005422 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005423 }
5424
5425 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005426 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005427
5428 {
5429 const char * const strs[2] = { "QUIT\r\n" };
5430 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5431 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005432 }
5433
Christopher Faulet61cc8522020-04-20 14:54:42 +02005434 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5435 goto error;
5436
5437 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5438 task_wakeup(check->task, TASK_WOKEN_MSG);
5439 LIST_ADDQ(&q->email_alerts, &alert->list);
5440 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5441 return 1;
5442
5443error:
5444 email_alert_free(alert);
5445 return 0;
5446}
5447
5448static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5449{
5450 int i;
5451 struct mailer *mailer;
5452
5453 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5454 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5455 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5456 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5457 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005458 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005459 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005460
Christopher Faulet61cc8522020-04-20 14:54:42 +02005461 return;
5462}
5463
5464/*
5465 * Send email alert if configured.
5466 */
5467void send_email_alert(struct server *s, int level, const char *format, ...)
5468{
5469 va_list argp;
5470 char buf[1024];
5471 int len;
5472 struct proxy *p = s->proxy;
5473
5474 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5475 return;
5476
5477 va_start(argp, format);
5478 len = vsnprintf(buf, sizeof(buf), format, argp);
5479 va_end(argp);
5480
5481 if (len < 0 || len >= sizeof(buf)) {
5482 ha_alert("Email alert [%s] could not format message\n", p->id);
5483 return;
5484 }
5485
5486 enqueue_email_alert(p, s, buf);
5487}
5488
5489/**************************************************************************/
5490/************************** Check sample fetches **************************/
5491/**************************************************************************/
5492/* extracts check payload at a fixed position and length */
5493static int
5494smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5495{
5496 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5497 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5498 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5499 struct buffer *buf;
5500
5501 if (!check)
5502 return 0;
5503
5504 buf = &check->bi;
5505 if (buf_offset > b_data(buf))
5506 goto no_match;
5507 if (buf_offset + buf_size > b_data(buf))
5508 buf_size = 0;
5509
5510 /* init chunk as read only */
5511 smp->data.type = SMP_T_STR;
5512 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5513 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5514
5515 return 1;
5516
5517 no_match:
5518 smp->flags = 0;
5519 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005520}
5521
Christopher Faulet61cc8522020-04-20 14:54:42 +02005522static struct sample_fetch_kw_list smp_kws = {ILH, {
5523 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5524 { /* END */ },
5525}};
5526
5527INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5528
5529
5530/**************************************************************************/
5531/************************ Check's parsing functions ***********************/
5532/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005533/* Parses the "tcp-check" proxy keyword */
5534static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5535 struct proxy *defpx, const char *file, int line,
5536 char **errmsg)
5537{
Christopher Faulet404f9192020-04-09 23:13:54 +02005538 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005539 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005540 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005541
5542 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5543 ret = 1;
5544
Christopher Faulet404f9192020-04-09 23:13:54 +02005545 /* Deduce the ruleset name from the proxy info */
5546 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5547 ((curpx == defpx) ? "defaults" : curpx->id),
5548 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005549
Christopher Faulet61cc8522020-04-20 14:54:42 +02005550 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005551 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005552 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005553 if (rs == NULL) {
5554 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005555 goto error;
5556 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005557 }
5558
Gaetan Rivet5301b012020-02-25 17:19:17 +01005559 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005560 if (!LIST_ISEMPTY(&rs->rules)) {
5561 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005562 index = chk->index + 1;
5563 }
5564
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005565 cur_arg = 1;
5566 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005567 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005568 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005569 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005570 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005571 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005572 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005573 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005574 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005575 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5576
5577 if (!kw) {
5578 action_kw_tcp_check_build_list(&trash);
5579 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5580 "%s%s. but got '%s'",
5581 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5582 goto error;
5583 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005584 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005585 }
5586
5587 if (!chk) {
5588 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5589 goto error;
5590 }
Christopher Faulet528f4812020-04-28 10:47:28 +02005591 ret = (ret || (*errmsg != NULL)); /* Handle warning */
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005592
5593 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005594 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005595 LIST_ADDQ(&rs->rules, &chk->list);
5596
5597 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005598 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005599 /* Use this ruleset if the proxy already has tcp-check enabled */
5600 curpx->tcpcheck_rules.list = &rs->rules;
5601 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5602 }
5603 else {
5604 /* mark this ruleset as unused for now */
5605 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5606 }
5607
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005608 return ret;
5609
5610 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005611 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005612 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005613 return -1;
5614}
5615
Christopher Faulet51b129f2020-04-09 15:54:18 +02005616/* Parses the "http-check" proxy keyword */
5617static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5618 struct proxy *defpx, const char *file, int line,
5619 char **errmsg)
5620{
Christopher Faulete5870d82020-04-15 11:32:03 +02005621 struct tcpcheck_ruleset *rs = NULL;
5622 struct tcpcheck_rule *chk = NULL;
5623 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005624
5625 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5626 ret = 1;
5627
5628 cur_arg = 1;
5629 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5630 /* enable a graceful server shutdown on an HTTP 404 response */
5631 curpx->options |= PR_O_DISABLE404;
5632 if (too_many_args(1, args, errmsg, NULL))
5633 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005634 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005635 }
5636 else if (strcmp(args[cur_arg], "send-state") == 0) {
5637 /* enable emission of the apparent state of a server in HTTP checks */
5638 curpx->options2 |= PR_O2_CHK_SNDST;
5639 if (too_many_args(1, args, errmsg, NULL))
5640 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005641 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005642 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005643
Christopher Faulete5870d82020-04-15 11:32:03 +02005644 /* Deduce the ruleset name from the proxy info */
5645 chunk_printf(&trash, "*http-check-%s_%s-%d",
5646 ((curpx == defpx) ? "defaults" : curpx->id),
5647 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005648
Christopher Faulet61cc8522020-04-20 14:54:42 +02005649 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005650 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005651 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005652 if (rs == NULL) {
5653 memprintf(errmsg, "out of memory.\n");
5654 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005655 }
5656 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005657
Christopher Faulete5870d82020-04-15 11:32:03 +02005658 index = 0;
5659 if (!LIST_ISEMPTY(&rs->rules)) {
5660 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5661 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5662 index = chk->index + 1;
5663 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005664
Christopher Faulete5870d82020-04-15 11:32:03 +02005665 if (strcmp(args[cur_arg], "connect") == 0)
5666 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5667 else if (strcmp(args[cur_arg], "send") == 0)
5668 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5669 else if (strcmp(args[cur_arg], "expect") == 0)
5670 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5671 file, line, errmsg);
5672 else if (strcmp(args[cur_arg], "comment") == 0)
5673 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5674 else {
5675 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005676
Christopher Faulete5870d82020-04-15 11:32:03 +02005677 if (!kw) {
5678 action_kw_tcp_check_build_list(&trash);
5679 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5680 " 'send', 'expect'%s%s. but got '%s'",
5681 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5682 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005683 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005684 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5685 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005686
Christopher Faulete5870d82020-04-15 11:32:03 +02005687 if (!chk) {
5688 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5689 goto error;
5690 }
5691 ret = (*errmsg != NULL); /* Handle warning */
5692
5693 chk->index = index;
5694 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5695 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5696 /* Use this ruleset if the proxy already has http-check enabled */
5697 curpx->tcpcheck_rules.list = &rs->rules;
5698 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5699 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5700 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5701 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005702 goto error;
5703 }
5704 }
5705 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005706 /* mark this ruleset as unused for now */
5707 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5708 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005709 }
5710
Christopher Faulete5870d82020-04-15 11:32:03 +02005711 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005712 return ret;
5713
5714 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005715 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005716 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005717 return -1;
5718}
5719
Christopher Faulete9111b62020-04-09 18:12:08 +02005720/* Parses the "external-check" proxy keyword */
5721static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5722 struct proxy *defpx, const char *file, int line,
5723 char **errmsg)
5724{
5725 int cur_arg, ret = 0;
5726
5727 cur_arg = 1;
5728 if (!*(args[cur_arg])) {
5729 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5730 goto error;
5731 }
5732
5733 if (strcmp(args[cur_arg], "command") == 0) {
5734 if (too_many_args(2, args, errmsg, NULL))
5735 goto error;
5736 if (!*(args[cur_arg+1])) {
5737 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5738 goto error;
5739 }
5740 free(curpx->check_command);
5741 curpx->check_command = strdup(args[cur_arg+1]);
5742 }
5743 else if (strcmp(args[cur_arg], "path") == 0) {
5744 if (too_many_args(2, args, errmsg, NULL))
5745 goto error;
5746 if (!*(args[cur_arg+1])) {
5747 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5748 goto error;
5749 }
5750 free(curpx->check_path);
5751 curpx->check_path = strdup(args[cur_arg+1]);
5752 }
5753 else {
5754 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5755 args[0], args[1]);
5756 goto error;
5757 }
5758
5759 ret = (*errmsg != NULL); /* Handle warning */
5760 return ret;
5761
5762error:
5763 return -1;
5764}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005765
Christopher Faulet430e4802020-04-09 15:28:16 +02005766/* Parses the "option tcp-check" proxy keyword */
5767int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5768 const char *file, int line)
5769{
Christopher Faulet404f9192020-04-09 23:13:54 +02005770 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005771 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5772 int err_code = 0;
5773
5774 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5775 err_code |= ERR_WARN;
5776
5777 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5778 goto out;
5779
Christopher Faulet404f9192020-04-09 23:13:54 +02005780 curpx->options2 &= ~PR_O2_CHK_ANY;
5781 curpx->options2 |= PR_O2_TCPCHK_CHK;
5782
Christopher Fauletd7e63962020-04-17 20:15:59 +02005783 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005784 /* If a tcp-check rulesset is already set, do nothing */
5785 if (rules->list)
5786 goto out;
5787
5788 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5789 * get it.
5790 */
5791 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5792 goto curpx_ruleset;
5793
5794 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5795 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005796 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005797 if (rs)
5798 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005799 }
5800
Christopher Faulet404f9192020-04-09 23:13:54 +02005801 curpx_ruleset:
5802 /* Deduce the ruleset name from the proxy info */
5803 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5804 ((curpx == defpx) ? "defaults" : curpx->id),
5805 curpx->conf.file, curpx->conf.line);
5806
Christopher Faulet61cc8522020-04-20 14:54:42 +02005807 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005808 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005809 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005810 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005811 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5812 goto error;
5813 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005814 }
5815
Christopher Faulet404f9192020-04-09 23:13:54 +02005816 ruleset_found:
5817 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005818 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005819 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005820
5821 out:
5822 return err_code;
5823
5824 error:
5825 err_code |= ERR_ALERT | ERR_FATAL;
5826 goto out;
5827}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005828
5829/* Parses the "option redis-check" proxy keyword */
5830int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5831 const char *file, int line)
5832{
5833 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5834 static char *redis_res = "+PONG\r\n";
5835
5836 struct tcpcheck_ruleset *rs = NULL;
5837 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5838 struct tcpcheck_rule *chk;
5839 char *errmsg = NULL;
5840 int err_code = 0;
5841
5842 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5843 err_code |= ERR_WARN;
5844
5845 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5846 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005847
5848 curpx->options2 &= ~PR_O2_CHK_ANY;
5849 curpx->options2 |= PR_O2_TCPCHK_CHK;
5850
5851 free_tcpcheck_vars(&rules->preset_vars);
5852 rules->list = NULL;
5853 rules->flags = 0;
5854
Christopher Faulet61cc8522020-04-20 14:54:42 +02005855 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005856 if (rs)
5857 goto ruleset_found;
5858
Christopher Faulet61cc8522020-04-20 14:54:42 +02005859 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005860 if (rs == NULL) {
5861 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5862 goto error;
5863 }
5864
5865 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5866 1, curpx, &rs->rules, file, line, &errmsg);
5867 if (!chk) {
5868 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5869 goto error;
5870 }
5871 chk->index = 0;
5872 LIST_ADDQ(&rs->rules, &chk->list);
5873
5874 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5875 "error-status", "L7STS",
5876 "on-error", "%[check.payload(),cut_crlf]",
5877 "on-success", "Redis server is ok",
5878 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005879 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005880 if (!chk) {
5881 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5882 goto error;
5883 }
5884 chk->index = 1;
5885 LIST_ADDQ(&rs->rules, &chk->list);
5886
Christopher Fauletd7cee712020-04-21 13:45:00 +02005887 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005888
5889 ruleset_found:
5890 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005891 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005892
5893 out:
5894 free(errmsg);
5895 return err_code;
5896
5897 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005898 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005899 err_code |= ERR_ALERT | ERR_FATAL;
5900 goto out;
5901}
5902
Christopher Faulet811f78c2020-04-01 11:10:27 +02005903
5904/* Parses the "option ssl-hello-chk" proxy keyword */
5905int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5906 const char *file, int line)
5907{
5908 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5909 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5910 *
5911 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5912 */
5913 static char sslv3_client_hello[] = {
5914 "16" /* ContentType : 0x16 = Hanshake */
5915 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5916 "0079" /* ContentLength : 0x79 bytes after this one */
5917 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5918 "000075" /* HandshakeLength : 0x75 bytes after this one */
5919 "0300" /* Hello Version : 0x0300 = v3 */
5920 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5921 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5922 "00" /* Session ID length : empty (no session ID) */
5923 "004E" /* Cipher Suite Length : 78 bytes after this one */
5924 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5925 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5926 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5927 "000D" "000E" "000F" "0010" /* various bit lengths, */
5928 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5929 "0015" "0016" "0017" "0018"
5930 "0019" "001A" "001B" "002F"
5931 "0030" "0031" "0032" "0033"
5932 "0034" "0035" "0036" "0037"
5933 "0038" "0039" "003A"
5934 "01" /* Compression Length : 0x01 = 1 byte for types */
5935 "00" /* Compression Type : 0x00 = NULL compression */
5936 };
5937
5938 struct tcpcheck_ruleset *rs = NULL;
5939 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5940 struct tcpcheck_rule *chk;
5941 char *errmsg = NULL;
5942 int err_code = 0;
5943
5944 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5945 err_code |= ERR_WARN;
5946
5947 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5948 goto out;
5949
Christopher Faulet811f78c2020-04-01 11:10:27 +02005950 curpx->options2 &= ~PR_O2_CHK_ANY;
5951 curpx->options2 |= PR_O2_TCPCHK_CHK;
5952
5953 free_tcpcheck_vars(&rules->preset_vars);
5954 rules->list = NULL;
5955 rules->flags = 0;
5956
Christopher Faulet61cc8522020-04-20 14:54:42 +02005957 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005958 if (rs)
5959 goto ruleset_found;
5960
Christopher Faulet61cc8522020-04-20 14:54:42 +02005961 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005962 if (rs == NULL) {
5963 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5964 goto error;
5965 }
5966
5967 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5968 1, curpx, &rs->rules, file, line, &errmsg);
5969 if (!chk) {
5970 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5971 goto error;
5972 }
5973 chk->index = 0;
5974 LIST_ADDQ(&rs->rules, &chk->list);
5975
5976 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005977 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005978 "error-status", "L6RSP", "tout-status", "L6TOUT",
5979 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005980 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005981 if (!chk) {
5982 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5983 goto error;
5984 }
5985 chk->index = 1;
5986 LIST_ADDQ(&rs->rules, &chk->list);
5987
Christopher Fauletd7cee712020-04-21 13:45:00 +02005988 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005989
5990 ruleset_found:
5991 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005992 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005993
5994 out:
5995 free(errmsg);
5996 return err_code;
5997
5998 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005999 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02006000 err_code |= ERR_ALERT | ERR_FATAL;
6001 goto out;
6002}
6003
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006004/* Parses the "option smtpchk" proxy keyword */
6005int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6006 const char *file, int line)
6007{
6008 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
6009
6010 struct tcpcheck_ruleset *rs = NULL;
6011 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6012 struct tcpcheck_rule *chk;
6013 struct tcpcheck_var *var = NULL;
6014 char *cmd = NULL, *errmsg = NULL;
6015 int err_code = 0;
6016
6017 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6018 err_code |= ERR_WARN;
6019
6020 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6021 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006022
6023 curpx->options2 &= ~PR_O2_CHK_ANY;
6024 curpx->options2 |= PR_O2_TCPCHK_CHK;
6025
6026 free_tcpcheck_vars(&rules->preset_vars);
6027 rules->list = NULL;
6028 rules->flags = 0;
6029
6030 cur_arg += 2;
6031 if (*args[cur_arg] && *args[cur_arg+1] &&
6032 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6033 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6034 if (cmd)
6035 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6036 }
6037 else {
6038 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6039 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6040 cmd = strdup("HELO localhost");
6041 }
6042
Christopher Fauletb61caf42020-04-21 10:57:42 +02006043 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006044 if (cmd == NULL || var == NULL) {
6045 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6046 goto error;
6047 }
6048 var->data.type = SMP_T_STR;
6049 var->data.u.str.area = cmd;
6050 var->data.u.str.data = strlen(cmd);
6051 LIST_INIT(&var->list);
6052 LIST_ADDQ(&rules->preset_vars, &var->list);
6053 cmd = NULL;
6054 var = NULL;
6055
Christopher Faulet61cc8522020-04-20 14:54:42 +02006056 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006057 if (rs)
6058 goto ruleset_found;
6059
Christopher Faulet61cc8522020-04-20 14:54:42 +02006060 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006061 if (rs == NULL) {
6062 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6063 goto error;
6064 }
6065
6066 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6067 1, curpx, &rs->rules, file, line, &errmsg);
6068 if (!chk) {
6069 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6070 goto error;
6071 }
6072 chk->index = 0;
6073 LIST_ADDQ(&rs->rules, &chk->list);
6074
6075 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6076 "min-recv", "4",
6077 "error-status", "L7RSP",
6078 "on-error", "%[check.payload(),cut_crlf]",
6079 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006080 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006081 if (!chk) {
6082 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6083 goto error;
6084 }
6085 chk->index = 1;
6086 LIST_ADDQ(&rs->rules, &chk->list);
6087
6088 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6089 "min-recv", "4",
6090 "error-status", "L7STS",
6091 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6092 "status-code", "check.payload(0,3)",
6093 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006094 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006095 if (!chk) {
6096 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6097 goto error;
6098 }
6099 chk->index = 2;
6100 LIST_ADDQ(&rs->rules, &chk->list);
6101
6102 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6103 1, curpx, &rs->rules, file, line, &errmsg);
6104 if (!chk) {
6105 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6106 goto error;
6107 }
6108 chk->index = 3;
6109 LIST_ADDQ(&rs->rules, &chk->list);
6110
6111 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6112 "min-recv", "4",
6113 "error-status", "L7STS",
6114 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6115 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6116 "status-code", "check.payload(0,3)",
6117 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006118 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006119 if (!chk) {
6120 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6121 goto error;
6122 }
6123 chk->index = 4;
6124 LIST_ADDQ(&rs->rules, &chk->list);
6125
Christopher Fauletd7cee712020-04-21 13:45:00 +02006126 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006127
6128 ruleset_found:
6129 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006130 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006131
6132 out:
6133 free(errmsg);
6134 return err_code;
6135
6136 error:
6137 free(cmd);
6138 free(var);
6139 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006140 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006141 err_code |= ERR_ALERT | ERR_FATAL;
6142 goto out;
6143}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006144
Christopher Fauletce355072020-04-02 11:44:39 +02006145/* Parses the "option pgsql-check" proxy keyword */
6146int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6147 const char *file, int line)
6148{
6149 static char pgsql_req[] = {
6150 "%[var(check.plen),htonl,hex]" /* The packet length*/
6151 "00030000" /* the version 3.0 */
6152 "7573657200" /* "user" key */
6153 "%[var(check.username),hex]00" /* the username */
6154 "00"
6155 };
6156
6157 struct tcpcheck_ruleset *rs = NULL;
6158 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6159 struct tcpcheck_rule *chk;
6160 struct tcpcheck_var *var = NULL;
6161 char *user = NULL, *errmsg = NULL;
6162 size_t packetlen = 0;
6163 int err_code = 0;
6164
6165 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6166 err_code |= ERR_WARN;
6167
6168 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6169 goto out;
6170
Christopher Fauletce355072020-04-02 11:44:39 +02006171 curpx->options2 &= ~PR_O2_CHK_ANY;
6172 curpx->options2 |= PR_O2_TCPCHK_CHK;
6173
6174 free_tcpcheck_vars(&rules->preset_vars);
6175 rules->list = NULL;
6176 rules->flags = 0;
6177
6178 cur_arg += 2;
6179 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6180 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6181 file, line, args[0], args[1]);
6182 goto error;
6183 }
6184 if (strcmp(args[cur_arg], "user") == 0) {
6185 packetlen = 15 + strlen(args[cur_arg+1]);
6186 user = strdup(args[cur_arg+1]);
6187
Christopher Fauletb61caf42020-04-21 10:57:42 +02006188 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006189 if (user == NULL || var == NULL) {
6190 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6191 goto error;
6192 }
6193 var->data.type = SMP_T_STR;
6194 var->data.u.str.area = user;
6195 var->data.u.str.data = strlen(user);
6196 LIST_INIT(&var->list);
6197 LIST_ADDQ(&rules->preset_vars, &var->list);
6198 user = NULL;
6199 var = NULL;
6200
Christopher Fauletb61caf42020-04-21 10:57:42 +02006201 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006202 if (var == NULL) {
6203 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6204 goto error;
6205 }
6206 var->data.type = SMP_T_SINT;
6207 var->data.u.sint = packetlen;
6208 LIST_INIT(&var->list);
6209 LIST_ADDQ(&rules->preset_vars, &var->list);
6210 var = NULL;
6211 }
6212 else {
6213 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6214 file, line, args[0], args[1]);
6215 goto error;
6216 }
6217
Christopher Faulet61cc8522020-04-20 14:54:42 +02006218 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006219 if (rs)
6220 goto ruleset_found;
6221
Christopher Faulet61cc8522020-04-20 14:54:42 +02006222 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006223 if (rs == NULL) {
6224 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6225 goto error;
6226 }
6227
6228 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6229 1, curpx, &rs->rules, file, line, &errmsg);
6230 if (!chk) {
6231 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6232 goto error;
6233 }
6234 chk->index = 0;
6235 LIST_ADDQ(&rs->rules, &chk->list);
6236
6237 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6238 1, curpx, &rs->rules, file, line, &errmsg);
6239 if (!chk) {
6240 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6241 goto error;
6242 }
6243 chk->index = 1;
6244 LIST_ADDQ(&rs->rules, &chk->list);
6245
6246 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6247 "min-recv", "5",
6248 "error-status", "L7RSP",
6249 "on-error", "%[check.payload(6,0)]",
6250 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006251 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006252 if (!chk) {
6253 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6254 goto error;
6255 }
6256 chk->index = 2;
6257 LIST_ADDQ(&rs->rules, &chk->list);
6258
Christopher Fauletb841c742020-04-27 18:29:49 +02006259 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 +02006260 "min-recv", "9",
6261 "error-status", "L7STS",
6262 "on-success", "PostgreSQL server is ok",
6263 "on-error", "PostgreSQL unknown error",
6264 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006265 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006266 if (!chk) {
6267 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6268 goto error;
6269 }
6270 chk->index = 3;
6271 LIST_ADDQ(&rs->rules, &chk->list);
6272
Christopher Fauletd7cee712020-04-21 13:45:00 +02006273 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006274
6275 ruleset_found:
6276 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006277 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006278
6279 out:
6280 free(errmsg);
6281 return err_code;
6282
6283 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006284 free(user);
6285 free(var);
6286 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006287 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006288 err_code |= ERR_ALERT | ERR_FATAL;
6289 goto out;
6290}
6291
6292
6293/* Parses the "option mysql-check" proxy keyword */
6294int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6295 const char *file, int line)
6296{
6297 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6298 * const char mysql40_client_auth_pkt[] = {
6299 * "\x0e\x00\x00" // packet length
6300 * "\x01" // packet number
6301 * "\x00\x00" // client capabilities
6302 * "\x00\x00\x01" // max packet
6303 * "haproxy\x00" // username (null terminated string)
6304 * "\x00" // filler (always 0x00)
6305 * "\x01\x00\x00" // packet length
6306 * "\x00" // packet number
6307 * "\x01" // COM_QUIT command
6308 * };
6309 */
6310 static char mysql40_rsname[] = "*mysql40-check";
6311 static char mysql40_req[] = {
6312 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6313 "0080" /* client capabilities */
6314 "000001" /* max packet */
6315 "%[var(check.username),hex]00" /* the username */
6316 "00" /* filler (always 0x00) */
6317 "010000" /* packet length*/
6318 "00" /* sequence ID */
6319 "01" /* COM_QUIT command */
6320 };
6321
6322 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6323 * const char mysql41_client_auth_pkt[] = {
6324 * "\x0e\x00\x00\" // packet length
6325 * "\x01" // packet number
6326 * "\x00\x00\x00\x00" // client capabilities
6327 * "\x00\x00\x00\x01" // max packet
6328 * "\x21" // character set (UTF-8)
6329 * char[23] // All zeroes
6330 * "haproxy\x00" // username (null terminated string)
6331 * "\x00" // filler (always 0x00)
6332 * "\x01\x00\x00" // packet length
6333 * "\x00" // packet number
6334 * "\x01" // COM_QUIT command
6335 * };
6336 */
6337 static char mysql41_rsname[] = "*mysql41-check";
6338 static char mysql41_req[] = {
6339 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6340 "00820000" /* client capabilities */
6341 "00800001" /* max packet */
6342 "21" /* character set (UTF-8) */
6343 "000000000000000000000000" /* 23 bytes, al zeroes */
6344 "0000000000000000000000"
6345 "%[var(check.username),hex]00" /* the username */
6346 "00" /* filler (always 0x00) */
6347 "010000" /* packet length*/
6348 "00" /* sequence ID */
6349 "01" /* COM_QUIT command */
6350 };
6351
6352 struct tcpcheck_ruleset *rs = NULL;
6353 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6354 struct tcpcheck_rule *chk;
6355 struct tcpcheck_var *var = NULL;
6356 char *mysql_rsname = "*mysql-check";
6357 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6358 int index = 0, err_code = 0;
6359
6360 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6361 err_code |= ERR_WARN;
6362
6363 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6364 goto out;
6365
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006366 curpx->options2 &= ~PR_O2_CHK_ANY;
6367 curpx->options2 |= PR_O2_TCPCHK_CHK;
6368
6369 free_tcpcheck_vars(&rules->preset_vars);
6370 rules->list = NULL;
6371 rules->flags = 0;
6372
6373 cur_arg += 2;
6374 if (*args[cur_arg]) {
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006375 int packetlen, userlen;
6376
6377 if (strcmp(args[cur_arg], "user") != 0) {
6378 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6379 file, line, args[0], args[1], args[cur_arg]);
6380 goto error;
6381 }
6382
6383 if (*(args[cur_arg+1]) == 0) {
6384 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6385 file, line, args[0], args[1], args[cur_arg]);
6386 goto error;
6387 }
6388
6389 hdr = calloc(4, sizeof(*hdr));
6390 user = strdup(args[cur_arg+1]);
6391 userlen = strlen(args[cur_arg+1]);
6392
6393 if (hdr == NULL || user == NULL) {
6394 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6395 goto error;
6396 }
6397
6398 if (*args[cur_arg+2]) {
6399 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6400 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6401 file, line, args[cur_arg], args[cur_arg+2]);
6402 goto error;
6403 }
6404 packetlen = userlen + 7 + 27;
6405 mysql_req = mysql41_req;
6406 mysql_rsname = mysql41_rsname;
6407 }
6408 else {
6409 packetlen = userlen + 7;
6410 mysql_req = mysql40_req;
6411 mysql_rsname = mysql40_rsname;
6412 }
6413
6414 hdr[0] = (unsigned char)(packetlen & 0xff);
6415 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6416 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6417 hdr[3] = 1;
6418
Christopher Fauletb61caf42020-04-21 10:57:42 +02006419 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006420 if (var == NULL) {
6421 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6422 goto error;
6423 }
6424 var->data.type = SMP_T_STR;
6425 var->data.u.str.area = hdr;
6426 var->data.u.str.data = 4;
6427 LIST_INIT(&var->list);
6428 LIST_ADDQ(&rules->preset_vars, &var->list);
6429 hdr = NULL;
6430 var = NULL;
6431
Christopher Fauletb61caf42020-04-21 10:57:42 +02006432 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006433 if (var == NULL) {
6434 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6435 goto error;
6436 }
6437 var->data.type = SMP_T_STR;
6438 var->data.u.str.area = user;
6439 var->data.u.str.data = strlen(user);
6440 LIST_INIT(&var->list);
6441 LIST_ADDQ(&rules->preset_vars, &var->list);
6442 user = NULL;
6443 var = NULL;
6444 }
6445
Christopher Faulet61cc8522020-04-20 14:54:42 +02006446 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006447 if (rs)
6448 goto ruleset_found;
6449
Christopher Faulet61cc8522020-04-20 14:54:42 +02006450 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006451 if (rs == NULL) {
6452 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6453 goto error;
6454 }
6455
6456 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6457 1, curpx, &rs->rules, file, line, &errmsg);
6458 if (!chk) {
6459 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6460 goto error;
6461 }
6462 chk->index = index++;
6463 LIST_ADDQ(&rs->rules, &chk->list);
6464
6465 if (mysql_req) {
6466 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6467 1, curpx, &rs->rules, file, line, &errmsg);
6468 if (!chk) {
6469 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6470 goto error;
6471 }
6472 chk->index = index++;
6473 LIST_ADDQ(&rs->rules, &chk->list);
6474 }
6475
6476 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006477 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006478 if (!chk) {
6479 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6480 goto error;
6481 }
6482 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6483 chk->index = index++;
6484 LIST_ADDQ(&rs->rules, &chk->list);
6485
6486 if (mysql_req) {
6487 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006488 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006489 if (!chk) {
6490 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6491 goto error;
6492 }
6493 chk->expect.custom = tcpcheck_mysql_expect_ok;
6494 chk->index = index++;
6495 LIST_ADDQ(&rs->rules, &chk->list);
6496 }
6497
Christopher Fauletd7cee712020-04-21 13:45:00 +02006498 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006499
6500 ruleset_found:
6501 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006502 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006503
6504 out:
6505 free(errmsg);
6506 return err_code;
6507
6508 error:
6509 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006510 free(user);
6511 free(var);
6512 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006513 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006514 err_code |= ERR_ALERT | ERR_FATAL;
6515 goto out;
6516}
6517
Christopher Faulet1997eca2020-04-03 23:13:50 +02006518int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6519 const char *file, int line)
6520{
6521 static char *ldap_req = "300C020101600702010304008000";
6522
6523 struct tcpcheck_ruleset *rs = NULL;
6524 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6525 struct tcpcheck_rule *chk;
6526 char *errmsg = NULL;
6527 int err_code = 0;
6528
6529 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6530 err_code |= ERR_WARN;
6531
6532 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6533 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006534
6535 curpx->options2 &= ~PR_O2_CHK_ANY;
6536 curpx->options2 |= PR_O2_TCPCHK_CHK;
6537
6538 free_tcpcheck_vars(&rules->preset_vars);
6539 rules->list = NULL;
6540 rules->flags = 0;
6541
Christopher Faulet61cc8522020-04-20 14:54:42 +02006542 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006543 if (rs)
6544 goto ruleset_found;
6545
Christopher Faulet61cc8522020-04-20 14:54:42 +02006546 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006547 if (rs == NULL) {
6548 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6549 goto error;
6550 }
6551
6552 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6553 1, curpx, &rs->rules, file, line, &errmsg);
6554 if (!chk) {
6555 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6556 goto error;
6557 }
6558 chk->index = 0;
6559 LIST_ADDQ(&rs->rules, &chk->list);
6560
6561 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6562 "min-recv", "14",
6563 "on-error", "Not LDAPv3 protocol",
6564 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006565 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006566 if (!chk) {
6567 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6568 goto error;
6569 }
6570 chk->index = 1;
6571 LIST_ADDQ(&rs->rules, &chk->list);
6572
6573 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006574 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006575 if (!chk) {
6576 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6577 goto error;
6578 }
6579 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6580 chk->index = 2;
6581 LIST_ADDQ(&rs->rules, &chk->list);
6582
Christopher Fauletd7cee712020-04-21 13:45:00 +02006583 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006584
6585 ruleset_found:
6586 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006587 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006588
6589 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006590 free(errmsg);
6591 return err_code;
6592
6593 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006594 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006595 err_code |= ERR_ALERT | ERR_FATAL;
6596 goto out;
6597}
6598
6599int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6600 const char *file, int line)
6601{
6602 struct tcpcheck_ruleset *rs = NULL;
6603 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6604 struct tcpcheck_rule *chk;
6605 char *spop_req = NULL;
6606 char *errmsg = NULL;
6607 int spop_len = 0, err_code = 0;
6608
6609 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6610 err_code |= ERR_WARN;
6611
6612 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6613 goto out;
6614
Christopher Faulet267b01b2020-04-04 10:27:09 +02006615 curpx->options2 &= ~PR_O2_CHK_ANY;
6616 curpx->options2 |= PR_O2_TCPCHK_CHK;
6617
6618 free_tcpcheck_vars(&rules->preset_vars);
6619 rules->list = NULL;
6620 rules->flags = 0;
6621
6622
Christopher Faulet61cc8522020-04-20 14:54:42 +02006623 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006624 if (rs)
6625 goto ruleset_found;
6626
Christopher Faulet61cc8522020-04-20 14:54:42 +02006627 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006628 if (rs == NULL) {
6629 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6630 goto error;
6631 }
6632
6633 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6634 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6635 goto error;
6636 }
6637 chunk_reset(&trash);
6638 dump_binary(&trash, spop_req, spop_len);
6639 trash.area[trash.data] = '\0';
6640
6641 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6642 1, curpx, &rs->rules, file, line, &errmsg);
6643 if (!chk) {
6644 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6645 goto error;
6646 }
6647 chk->index = 0;
6648 LIST_ADDQ(&rs->rules, &chk->list);
6649
6650 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006651 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006652 if (!chk) {
6653 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6654 goto error;
6655 }
6656 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6657 chk->index = 1;
6658 LIST_ADDQ(&rs->rules, &chk->list);
6659
Christopher Fauletd7cee712020-04-21 13:45:00 +02006660 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006661
6662 ruleset_found:
6663 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006664 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006665
6666 out:
6667 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006668 free(errmsg);
6669 return err_code;
6670
6671 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006672 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006673 err_code |= ERR_ALERT | ERR_FATAL;
6674 goto out;
6675}
Christopher Fauletce355072020-04-02 11:44:39 +02006676
Christopher Faulete5870d82020-04-15 11:32:03 +02006677
6678struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6679{
6680 struct tcpcheck_rule *chk = NULL;
6681 struct tcpcheck_http_hdr *hdr = NULL;
6682 char *meth = NULL, *uri = NULL, *vsn = NULL;
6683 char *hdrs, *body;
6684
6685 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6686 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6687 if (hdrs == body)
6688 hdrs = NULL;
6689 if (hdrs) {
6690 *hdrs = '\0';
6691 hdrs +=2;
6692 }
6693 if (body) {
6694 *body = '\0';
6695 body += 4;
6696 }
6697 if (hdrs || body) {
6698 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6699 " Please, consider to use 'http-check send' directive instead.");
6700 }
6701
6702 chk = calloc(1, sizeof(*chk));
6703 if (!chk) {
6704 memprintf(errmsg, "out of memory");
6705 goto error;
6706 }
6707 chk->action = TCPCHK_ACT_SEND;
6708 chk->send.type = TCPCHK_SEND_HTTP;
6709 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6710 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6711 LIST_INIT(&chk->send.http.hdrs);
6712
6713 /* Copy the method, uri and version */
6714 if (*args[cur_arg]) {
6715 if (!*args[cur_arg+1])
6716 uri = args[cur_arg];
6717 else
6718 meth = args[cur_arg];
6719 }
6720 if (*args[cur_arg+1])
6721 uri = args[cur_arg+1];
6722 if (*args[cur_arg+2])
6723 vsn = args[cur_arg+2];
6724
6725 if (meth) {
6726 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6727 chk->send.http.meth.str.area = strdup(meth);
6728 chk->send.http.meth.str.data = strlen(meth);
6729 if (!chk->send.http.meth.str.area) {
6730 memprintf(errmsg, "out of memory");
6731 goto error;
6732 }
6733 }
6734 if (uri) {
6735 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006736 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006737 memprintf(errmsg, "out of memory");
6738 goto error;
6739 }
6740 }
6741 if (vsn) {
6742 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006743 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006744 memprintf(errmsg, "out of memory");
6745 goto error;
6746 }
6747 }
6748
6749 /* Copy the header */
6750 if (hdrs) {
6751 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6752 struct h1m h1m;
6753 int i, ret;
6754
6755 /* Build and parse the request */
6756 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6757
6758 h1m.flags = H1_MF_HDRS_ONLY;
6759 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6760 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6761 &h1m, NULL);
6762 if (ret <= 0) {
6763 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6764 goto error;
6765 }
6766
Christopher Fauletb61caf42020-04-21 10:57:42 +02006767 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006768 hdr = calloc(1, sizeof(*hdr));
6769 if (!hdr) {
6770 memprintf(errmsg, "out of memory");
6771 goto error;
6772 }
6773 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006774 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006775 if (!hdr->name.ptr) {
6776 memprintf(errmsg, "out of memory");
6777 goto error;
6778 }
6779
Christopher Fauletb61caf42020-04-21 10:57:42 +02006780 ist0(tmp_hdrs[i].v);
6781 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 +02006782 goto error;
6783 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6784 }
6785 }
6786
6787 /* Copy the body */
6788 if (body) {
6789 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006790 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006791 memprintf(errmsg, "out of memory");
6792 goto error;
6793 }
6794 }
6795
6796 return chk;
6797
6798 error:
6799 free_tcpcheck_http_hdr(hdr);
6800 free_tcpcheck(chk, 0);
6801 return NULL;
6802}
6803
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006804int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6805 const char *file, int line)
6806{
Christopher Faulete5870d82020-04-15 11:32:03 +02006807 struct tcpcheck_ruleset *rs = NULL;
6808 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6809 struct tcpcheck_rule *chk;
6810 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006811 int err_code = 0;
6812
6813 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6814 err_code |= ERR_WARN;
6815
6816 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6817 goto out;
6818
Christopher Faulete5870d82020-04-15 11:32:03 +02006819 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6820 if (!chk) {
6821 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6822 goto error;
6823 }
6824 if (errmsg) {
6825 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6826 err_code |= ERR_WARN;
6827 free(errmsg);
6828 errmsg = NULL;
6829 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006830
Christopher Faulete5870d82020-04-15 11:32:03 +02006831 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006832 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006833 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006834
Christopher Faulete5870d82020-04-15 11:32:03 +02006835 free_tcpcheck_vars(&rules->preset_vars);
6836 rules->list = NULL;
6837 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006838
Christopher Faulete5870d82020-04-15 11:32:03 +02006839 /* Deduce the ruleset name from the proxy info */
6840 chunk_printf(&trash, "*http-check-%s_%s-%d",
6841 ((curpx == defpx) ? "defaults" : curpx->id),
6842 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006843
Christopher Faulet61cc8522020-04-20 14:54:42 +02006844 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006845 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006846 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006847 if (rs == NULL) {
6848 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6849 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006850 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006851 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006852
Christopher Faulete5870d82020-04-15 11:32:03 +02006853 rules->list = &rs->rules;
6854 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6855 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6856 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6857 rules->list = NULL;
6858 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006859 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006860
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006861 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006862 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006863 return err_code;
6864
6865 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006866 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006867 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006868 err_code |= ERR_ALERT | ERR_FATAL;
6869 goto out;
6870}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006871
Christopher Faulet6f557912020-04-09 15:58:50 +02006872int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6873 const char *file, int line)
6874{
6875 int err_code = 0;
6876
Christopher Faulet6f557912020-04-09 15:58:50 +02006877 curpx->options2 &= ~PR_O2_CHK_ANY;
6878 curpx->options2 |= PR_O2_EXT_CHK;
6879 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6880 goto out;
6881
6882 out:
6883 return err_code;
6884}
6885
Christopher Fauletce8111e2020-04-06 15:04:11 +02006886/* Parse the "addr" server keyword */
6887static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6888 char **errmsg)
6889{
6890 struct sockaddr_storage *sk;
6891 struct protocol *proto;
6892 int port1, port2, err_code = 0;
6893
6894
6895 if (!*args[*cur_arg+1]) {
6896 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6897 goto error;
6898 }
6899
6900 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6901 if (!sk) {
6902 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6903 goto error;
6904 }
6905
6906 proto = protocol_by_family(sk->ss_family);
6907 if (!proto || !proto->connect) {
6908 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6909 args[*cur_arg], args[*cur_arg+1]);
6910 goto error;
6911 }
6912
6913 if (port1 != port2) {
6914 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6915 args[*cur_arg], args[*cur_arg+1]);
6916 goto error;
6917 }
6918
6919 srv->check.addr = srv->agent.addr = *sk;
6920 srv->flags |= SRV_F_CHECKADDR;
6921 srv->flags |= SRV_F_AGENTADDR;
6922
6923 out:
6924 return err_code;
6925
6926 error:
6927 err_code |= ERR_ALERT | ERR_FATAL;
6928 goto out;
6929}
6930
6931
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006932/* Parse the "agent-addr" server keyword */
6933static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6934 char **errmsg)
6935{
6936 int err_code = 0;
6937
6938 if (!*(args[*cur_arg+1])) {
6939 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6940 goto error;
6941 }
6942 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6943 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6944 goto error;
6945 }
6946
6947 out:
6948 return err_code;
6949
6950 error:
6951 err_code |= ERR_ALERT | ERR_FATAL;
6952 goto out;
6953}
6954
6955/* Parse the "agent-check" server keyword */
6956static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6957 char **errmsg)
6958{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006959 struct tcpcheck_ruleset *rs = NULL;
6960 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6961 struct tcpcheck_rule *chk;
6962 int err_code = 0;
6963
6964 if (srv->do_agent)
6965 goto out;
6966
6967 if (!rules) {
6968 rules = calloc(1, sizeof(*rules));
6969 if (!rules) {
6970 memprintf(errmsg, "out of memory.");
6971 goto error;
6972 }
6973 LIST_INIT(&rules->preset_vars);
6974 srv->agent.tcpcheck_rules = rules;
6975 }
6976 rules->list = NULL;
6977 rules->flags = 0;
6978
Christopher Faulet61cc8522020-04-20 14:54:42 +02006979 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006980 if (rs)
6981 goto ruleset_found;
6982
Christopher Faulet61cc8522020-04-20 14:54:42 +02006983 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006984 if (rs == NULL) {
6985 memprintf(errmsg, "out of memory.");
6986 goto error;
6987 }
6988
6989 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6990 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6991 if (!chk) {
6992 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6993 goto error;
6994 }
6995 chk->index = 0;
6996 LIST_ADDQ(&rs->rules, &chk->list);
6997
6998 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006999 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
7000 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007001 if (!chk) {
7002 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
7003 goto error;
7004 }
7005 chk->expect.custom = tcpcheck_agent_expect_reply;
7006 chk->index = 1;
7007 LIST_ADDQ(&rs->rules, &chk->list);
7008
Christopher Fauletd7cee712020-04-21 13:45:00 +02007009 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007010
7011 ruleset_found:
7012 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007013 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007014 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007015
7016 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007017 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007018
7019 error:
7020 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02007021 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007022 err_code |= ERR_ALERT | ERR_FATAL;
7023 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007024}
7025
7026/* Parse the "agent-inter" server keyword */
7027static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7028 char **errmsg)
7029{
7030 const char *err = NULL;
7031 unsigned int delay;
7032 int err_code = 0;
7033
7034 if (!*(args[*cur_arg+1])) {
7035 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7036 goto error;
7037 }
7038
7039 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7040 if (err == PARSE_TIME_OVER) {
7041 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7042 args[*cur_arg+1], args[*cur_arg], srv->id);
7043 goto error;
7044 }
7045 else if (err == PARSE_TIME_UNDER) {
7046 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7047 args[*cur_arg+1], args[*cur_arg], srv->id);
7048 goto error;
7049 }
7050 else if (err) {
7051 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7052 *err, srv->id);
7053 goto error;
7054 }
7055 if (delay <= 0) {
7056 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7057 delay, args[*cur_arg], srv->id);
7058 goto error;
7059 }
7060 srv->agent.inter = delay;
7061
7062 out:
7063 return err_code;
7064
7065 error:
7066 err_code |= ERR_ALERT | ERR_FATAL;
7067 goto out;
7068}
7069
7070/* Parse the "agent-port" server keyword */
7071static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7072 char **errmsg)
7073{
7074 int err_code = 0;
7075
7076 if (!*(args[*cur_arg+1])) {
7077 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7078 goto error;
7079 }
7080
7081 global.maxsock++;
7082 srv->agent.port = atol(args[*cur_arg+1]);
7083
7084 out:
7085 return err_code;
7086
7087 error:
7088 err_code |= ERR_ALERT | ERR_FATAL;
7089 goto out;
7090}
7091
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007092int set_srv_agent_send(struct server *srv, const char *send)
7093{
7094 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7095 struct tcpcheck_var *var = NULL;
7096 char *str;
7097
7098 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007099 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007100 if (str == NULL || var == NULL)
7101 goto error;
7102
7103 free_tcpcheck_vars(&rules->preset_vars);
7104
7105 var->data.type = SMP_T_STR;
7106 var->data.u.str.area = str;
7107 var->data.u.str.data = strlen(str);
7108 LIST_INIT(&var->list);
7109 LIST_ADDQ(&rules->preset_vars, &var->list);
7110
7111 return 1;
7112
7113 error:
7114 free(str);
7115 free(var);
7116 return 0;
7117}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007118
7119/* Parse the "agent-send" server keyword */
7120static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7121 char **errmsg)
7122{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007123 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007124 int err_code = 0;
7125
7126 if (!*(args[*cur_arg+1])) {
7127 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7128 goto error;
7129 }
7130
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007131 if (!rules) {
7132 rules = calloc(1, sizeof(*rules));
7133 if (!rules) {
7134 memprintf(errmsg, "out of memory.");
7135 goto error;
7136 }
7137 LIST_INIT(&rules->preset_vars);
7138 srv->agent.tcpcheck_rules = rules;
7139 }
7140
7141 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007142 memprintf(errmsg, "out of memory.");
7143 goto error;
7144 }
7145
7146 out:
7147 return err_code;
7148
7149 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007150 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007151 err_code |= ERR_ALERT | ERR_FATAL;
7152 goto out;
7153}
7154
7155/* Parse the "no-agent-send" server keyword */
7156static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7157 char **errmsg)
7158{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007159 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007160 return 0;
7161}
7162
Christopher Fauletce8111e2020-04-06 15:04:11 +02007163/* Parse the "check" server keyword */
7164static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7165 char **errmsg)
7166{
7167 srv->do_check = 1;
7168 return 0;
7169}
7170
7171/* Parse the "check-send-proxy" server keyword */
7172static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7173 char **errmsg)
7174{
7175 srv->check.send_proxy = 1;
7176 return 0;
7177}
7178
7179/* Parse the "check-via-socks4" server keyword */
7180static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7181 char **errmsg)
7182{
7183 srv->check.via_socks4 = 1;
7184 return 0;
7185}
7186
7187/* Parse the "no-check" server keyword */
7188static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7189 char **errmsg)
7190{
7191 deinit_srv_check(srv);
7192 return 0;
7193}
7194
7195/* Parse the "no-check-send-proxy" server keyword */
7196static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7197 char **errmsg)
7198{
7199 srv->check.send_proxy = 0;
7200 return 0;
7201}
7202
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007203/* parse the "check-proto" server keyword */
7204static int srv_parse_check_proto(char **args, int *cur_arg,
7205 struct proxy *px, struct server *newsrv, char **err)
7206{
7207 int err_code = 0;
7208
7209 if (!*args[*cur_arg + 1]) {
7210 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7211 goto error;
7212 }
7213 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7214 if (!newsrv->check.mux_proto) {
7215 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7216 goto error;
7217 }
7218
7219 out:
7220 return err_code;
7221
7222 error:
7223 err_code |= ERR_ALERT | ERR_FATAL;
7224 goto out;
7225}
7226
7227
Christopher Fauletce8111e2020-04-06 15:04:11 +02007228/* Parse the "rise" server keyword */
7229static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7230 char **errmsg)
7231{
7232 int err_code = 0;
7233
7234 if (!*args[*cur_arg + 1]) {
7235 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7236 goto error;
7237 }
7238
7239 srv->check.rise = atol(args[*cur_arg+1]);
7240 if (srv->check.rise <= 0) {
7241 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7242 goto error;
7243 }
7244
7245 if (srv->check.health)
7246 srv->check.health = srv->check.rise;
7247
7248 out:
7249 return err_code;
7250
7251 error:
7252 deinit_srv_agent_check(srv);
7253 err_code |= ERR_ALERT | ERR_FATAL;
7254 goto out;
7255 return 0;
7256}
7257
7258/* Parse the "fall" server keyword */
7259static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7260 char **errmsg)
7261{
7262 int err_code = 0;
7263
7264 if (!*args[*cur_arg + 1]) {
7265 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7266 goto error;
7267 }
7268
7269 srv->check.fall = atol(args[*cur_arg+1]);
7270 if (srv->check.fall <= 0) {
7271 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7272 goto error;
7273 }
7274
7275 out:
7276 return err_code;
7277
7278 error:
7279 deinit_srv_agent_check(srv);
7280 err_code |= ERR_ALERT | ERR_FATAL;
7281 goto out;
7282 return 0;
7283}
7284
7285/* Parse the "inter" server keyword */
7286static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7287 char **errmsg)
7288{
7289 const char *err = NULL;
7290 unsigned int delay;
7291 int err_code = 0;
7292
7293 if (!*(args[*cur_arg+1])) {
7294 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7295 goto error;
7296 }
7297
7298 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7299 if (err == PARSE_TIME_OVER) {
7300 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7301 args[*cur_arg+1], args[*cur_arg], srv->id);
7302 goto error;
7303 }
7304 else if (err == PARSE_TIME_UNDER) {
7305 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7306 args[*cur_arg+1], args[*cur_arg], srv->id);
7307 goto error;
7308 }
7309 else if (err) {
7310 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7311 *err, srv->id);
7312 goto error;
7313 }
7314 if (delay <= 0) {
7315 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7316 delay, args[*cur_arg], srv->id);
7317 goto error;
7318 }
7319 srv->check.inter = delay;
7320
7321 out:
7322 return err_code;
7323
7324 error:
7325 err_code |= ERR_ALERT | ERR_FATAL;
7326 goto out;
7327}
7328
7329
7330/* Parse the "fastinter" server keyword */
7331static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7332 char **errmsg)
7333{
7334 const char *err = NULL;
7335 unsigned int delay;
7336 int err_code = 0;
7337
7338 if (!*(args[*cur_arg+1])) {
7339 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7340 goto error;
7341 }
7342
7343 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7344 if (err == PARSE_TIME_OVER) {
7345 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7346 args[*cur_arg+1], args[*cur_arg], srv->id);
7347 goto error;
7348 }
7349 else if (err == PARSE_TIME_UNDER) {
7350 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7351 args[*cur_arg+1], args[*cur_arg], srv->id);
7352 goto error;
7353 }
7354 else if (err) {
7355 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7356 *err, srv->id);
7357 goto error;
7358 }
7359 if (delay <= 0) {
7360 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7361 delay, args[*cur_arg], srv->id);
7362 goto error;
7363 }
7364 srv->check.fastinter = delay;
7365
7366 out:
7367 return err_code;
7368
7369 error:
7370 err_code |= ERR_ALERT | ERR_FATAL;
7371 goto out;
7372}
7373
7374
7375/* Parse the "downinter" server keyword */
7376static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7377 char **errmsg)
7378{
7379 const char *err = NULL;
7380 unsigned int delay;
7381 int err_code = 0;
7382
7383 if (!*(args[*cur_arg+1])) {
7384 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7385 goto error;
7386 }
7387
7388 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7389 if (err == PARSE_TIME_OVER) {
7390 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7391 args[*cur_arg+1], args[*cur_arg], srv->id);
7392 goto error;
7393 }
7394 else if (err == PARSE_TIME_UNDER) {
7395 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7396 args[*cur_arg+1], args[*cur_arg], srv->id);
7397 goto error;
7398 }
7399 else if (err) {
7400 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7401 *err, srv->id);
7402 goto error;
7403 }
7404 if (delay <= 0) {
7405 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7406 delay, args[*cur_arg], srv->id);
7407 goto error;
7408 }
7409 srv->check.downinter = delay;
7410
7411 out:
7412 return err_code;
7413
7414 error:
7415 err_code |= ERR_ALERT | ERR_FATAL;
7416 goto out;
7417}
7418
7419/* Parse the "port" server keyword */
7420static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7421 char **errmsg)
7422{
7423 int err_code = 0;
7424
7425 if (!*(args[*cur_arg+1])) {
7426 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7427 goto error;
7428 }
7429
7430 global.maxsock++;
7431 srv->check.port = atol(args[*cur_arg+1]);
7432 srv->flags |= SRV_F_CHECKPORT;
7433
7434 out:
7435 return err_code;
7436
7437 error:
7438 err_code |= ERR_ALERT | ERR_FATAL;
7439 goto out;
7440}
7441
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007442static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007443 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7444 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7445 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007446 { 0, NULL, NULL },
7447}};
7448
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007449static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007450 { "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 +02007451 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7452 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7453 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7454 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7455 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007456 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007457 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007458 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7459 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007460 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007461 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7462 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7463 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7464 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7465 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7466 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7467 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7468 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007469 { NULL, NULL, 0 },
7470}};
7471
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007472INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007473INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007474
Willy Tarreaubd741542010-03-16 18:46:54 +01007475/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007476 * Local variables:
7477 * c-indent-level: 8
7478 * c-basic-offset: 8
7479 * End:
7480 */